GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/quota_posix.cc
Date: 2025-02-09 02:34:19
Exec Total Coverage
Lines: 891 1192 74.7%
Branches: 733 1732 42.3%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * This module implements a "managed local cache".
5 * This way, we are able to track access times of files in the cache
6 * and remove files based on least recently used strategy.
7 *
8 * We setup another SQLite catalog, a "cache catalog", that helps us
9 * in the bookkeeping of files, file sizes and access times.
10 *
11 * We might choose to not manage the local cache. This is indicated
12 * by limit == 0 and everything succeeds in that case.
13 */
14
15 #define __STDC_LIMIT_MACROS
16 #define __STDC_FORMAT_MACROS
17
18
19 #include "quota_posix.h"
20
21 #include <dirent.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <inttypes.h>
25 #include <pthread.h>
26 #include <signal.h>
27 #include <stdint.h>
28 #include <sys/dir.h>
29 #include <sys/stat.h>
30 #ifndef __APPLE__
31 #include <sys/statfs.h>
32 #endif
33 #include <sys/statvfs.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <unistd.h>
37
38 #include <cassert>
39 #include <cstdio>
40 #include <cstdlib>
41 #include <cstring>
42 #include <limits>
43 #include <map>
44 #include <set>
45 #include <string>
46 #include <vector>
47
48 #include "crypto/hash.h"
49 #include "duplex_sqlite3.h"
50 #include "monitor.h"
51 #include "statistics.h"
52 #include "util/concurrency.h"
53 #include "util/exception.h"
54 #include "util/logging.h"
55 #include "util/platform.h"
56 #include "util/pointer.h"
57 #include "util/posix.h"
58 #include "util/smalloc.h"
59 #include "util/string.h"
60
61 using namespace std; // NOLINT
62
63
64 92 int PosixQuotaManager::BindReturnPipe(int pipe_wronly) {
65
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 2 times.
92 if (!shared_)
66 90 return pipe_wronly;
67
68 // Connect writer's end
69 int result =
70
3/6
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
2 open((workspace_dir_ + "/pipe" + StringifyInt(pipe_wronly)).c_str(),
71 O_WRONLY | O_NONBLOCK);
72
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (result >= 0) {
73 1 Nonblock2Block(result);
74 } else {
75 1 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
76 1 "failed to bind return pipe (%d)", errno);
77 }
78 2 return result;
79 }
80
81
82 26 void PosixQuotaManager::CheckHighPinWatermark() {
83 26 const uint64_t watermark = kHighPinWatermark*cleanup_threshold_/100;
84
3/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 23 times.
26 if ((cleanup_threshold_ > 0) && (pinned_ > watermark)) {
85 3 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
86 "high watermark of pinned files (%" PRIu64 "M > %" PRIu64 "M)",
87 3 pinned_/(1024*1024), watermark/(1024*1024));
88
2/4
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
3 BroadcastBackchannels("R"); // clients: please release pinned catalogs
89 }
90 26 }
91
92
93 void PosixQuotaManager::CleanupPipes() {
94 DIR *dirp = opendir(workspace_dir_.c_str());
95 assert(dirp != NULL);
96
97 platform_dirent64 *dent;
98 bool found_leftovers = false;
99 while ((dent = platform_readdir(dirp)) != NULL) {
100 const string name = dent->d_name;
101 const string path = workspace_dir_ + "/" + name;
102 platform_stat64 info;
103 int retval = platform_stat(path.c_str(), &info);
104 if (retval != 0)
105 continue;
106 if (S_ISFIFO(info.st_mode) && (name.substr(0, 4) == "pipe")) {
107 if (!found_leftovers) {
108 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
109 "removing left-over FIFOs from cache directory");
110 }
111 found_leftovers = true;
112 unlink(path.c_str());
113 }
114 }
115 closedir(dirp);
116 }
117
118
119 /**
120 * Cleans up in data cache, until cache size is below leave_size.
121 * The actual unlinking is done in a separate process (fork).
122 *
123 * \return True on success, false otherwise
124 */
125 9 bool PosixQuotaManager::Cleanup(const uint64_t leave_size) {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!spawned_)
127 return DoCleanup(leave_size);
128
129 bool result;
130 int pipe_cleanup[2];
131
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 MakeReturnPipe(pipe_cleanup);
132
133 9 LruCommand cmd;
134 9 cmd.command_type = kCleanup;
135 9 cmd.size = leave_size;
136 9 cmd.return_pipe = pipe_cleanup[1];
137
138
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
139
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 ManagedReadHalfPipe(pipe_cleanup[0], &result, sizeof(result));
140
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 CloseReturnPipe(pipe_cleanup);
141
142 9 return result;
143 }
144
145
146 87 void PosixQuotaManager::CloseDatabase() {
147
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_list_catalogs_) sqlite3_finalize(stmt_list_catalogs_);
148
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_list_pinned_) sqlite3_finalize(stmt_list_pinned_);
149
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_list_volatile_) sqlite3_finalize(stmt_list_volatile_);
150
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_list_) sqlite3_finalize(stmt_list_);
151
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_lru_) sqlite3_finalize(stmt_lru_);
152
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_rm_) sqlite3_finalize(stmt_rm_);
153
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_rm_batch_) sqlite3_finalize(stmt_rm_batch_);
154
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_size_) sqlite3_finalize(stmt_size_);
155
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_touch_) sqlite3_finalize(stmt_touch_);
156
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_unpin_) sqlite3_finalize(stmt_unpin_);
157
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_block_) sqlite3_finalize(stmt_block_);
158
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_unblock_) sqlite3_finalize(stmt_unblock_);
159
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (stmt_new_) sqlite3_finalize(stmt_new_);
160
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 if (database_) sqlite3_close(database_);
161 87 UnlockFile(fd_lock_cachedb_);
162
163 87 stmt_list_catalogs_ = NULL;
164 87 stmt_list_pinned_ = NULL;
165 87 stmt_list_volatile_ = NULL;
166 87 stmt_list_ = NULL;
167 87 stmt_rm_ = NULL;
168 87 stmt_rm_batch_ = NULL;
169 87 stmt_size_ = NULL;
170 87 stmt_touch_ = NULL;
171 87 stmt_unpin_ = NULL;
172 87 stmt_block_ = NULL;
173 87 stmt_unblock_ = NULL;
174 87 stmt_new_ = NULL;
175 87 database_ = NULL;
176
177 87 pinned_chunks_.clear();
178 87 }
179
180
181 87 void PosixQuotaManager::CloseReturnPipe(int pipe[2]) {
182
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 85 times.
87 if (shared_) {
183 2 close(pipe[0]);
184 2 UnlinkReturnPipe(pipe[1]);
185 } else {
186 85 ClosePipe(pipe);
187 }
188 87 }
189
190
191 100064 bool PosixQuotaManager::Contains(const string &hash_str) {
192 100064 bool result = false;
193
194 100064 sqlite3_bind_text(stmt_size_, 1, &hash_str[0], hash_str.length(),
195 SQLITE_STATIC);
196
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 100045 times.
100064 if (sqlite3_step(stmt_size_) == SQLITE_ROW)
197 19 result = true;
198 100064 sqlite3_reset(stmt_size_);
199 100064 LogCvmfs(kLogQuota, kLogDebug, "contains %s returns %d",
200 hash_str.c_str(), result);
201
202 100064 return result;
203 }
204
205
206 86 void PosixQuotaManager::CheckFreeSpace() {
207
3/4
✓ Branch 0 taken 86 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 85 times.
86 if ((limit_ == 0) || (gauge_ >= limit_))
208 1 return;
209
210 struct statvfs vfs_info;
211
1/2
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
85 int retval = statvfs((cache_dir_ + "/cachedb").c_str(), &vfs_info);
212
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (retval != 0) {
213 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
214 "failed to query %s for free space (%d)",
215 cache_dir_.c_str(), errno);
216 return;
217 }
218 85 int64_t free_space_byte = vfs_info.f_bavail * vfs_info.f_bsize;
219
1/2
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
85 LogCvmfs(kLogQuota, kLogDebug, "free space: %" PRId64 " MB",
220 free_space_byte / (1024 * 1024));
221
222 85 int64_t required_byte = limit_ - gauge_;
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (free_space_byte < required_byte) {
224 LogCvmfs(kLogQuota, kLogSyslogWarn,
225 "too little free space on the file system hosting the cache,"
226 " %" PRId64 " MB available",
227 free_space_byte / (1024 * 1024));
228 }
229 }
230
231
232 89 PosixQuotaManager *PosixQuotaManager::Create(
233 const string &cache_workspace,
234 const uint64_t limit,
235 const uint64_t cleanup_threshold,
236 const bool rebuild_database)
237 {
238
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 87 times.
89 if (cleanup_threshold >= limit) {
239 2 LogCvmfs(kLogQuota, kLogDebug, "invalid parameters: limit %" PRIu64 ", "
240 "cleanup_threshold %" PRIu64, limit, cleanup_threshold);
241 2 return NULL;
242 }
243
244 PosixQuotaManager *quota_manager =
245
1/2
✓ Branch 2 taken 87 times.
✗ Branch 3 not taken.
87 new PosixQuotaManager(limit, cleanup_threshold, cache_workspace);
246
247 // Initialize cache catalog
248
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 86 times.
87 if (!quota_manager->InitDatabase(rebuild_database)) {
249
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete quota_manager;
250 1 return NULL;
251 }
252 86 quota_manager->CheckFreeSpace();
253 86 MakePipe(quota_manager->pipe_lru_);
254
255 86 quota_manager->protocol_revision_ = kProtocolRevision;
256 86 quota_manager->initialized_ = true;
257 86 return quota_manager;
258 }
259
260
261 /**
262 * Connects to a running shared local quota manager. Creates one if necessary.
263 */
264 2 PosixQuotaManager *PosixQuotaManager::CreateShared(
265 const std::string &exe_path,
266 const std::string &cache_workspace,
267 const uint64_t limit,
268 const uint64_t cleanup_threshold,
269 bool foreground)
270 {
271 2 string cache_dir;
272 2 string workspace_dir;
273
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 ParseDirectories(cache_workspace, &cache_dir, &workspace_dir);
274
275 pid_t new_cachemgr_pid;
276
277 // Create lock file: only one fuse client at a time
278
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 const int fd_lockfile = LockFile(workspace_dir + "/lock_cachemgr");
279
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (fd_lockfile < 0) {
280
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "could not open lock file %s (%d)",
281
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 (workspace_dir + "/lock_cachemgr").c_str(), errno);
282 1 return NULL;
283 }
284
285 PosixQuotaManager *quota_mgr =
286
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 new PosixQuotaManager(limit, cleanup_threshold, cache_workspace);
287 1 quota_mgr->shared_ = true;
288 1 quota_mgr->spawned_ = true;
289
290 // Try to connect to pipe
291
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 const string fifo_path = workspace_dir + "/cachemgr";
292
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "trying to connect to existing pipe");
293
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 quota_mgr->pipe_lru_[1] = open(fifo_path.c_str(), O_WRONLY | O_NONBLOCK);
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (quota_mgr->pipe_lru_[1] >= 0) {
295 const int fd_lockfile_rw = open((workspace_dir + "/lock_cachemgr").c_str(), O_RDWR, 0600);
296 unsigned lockfile_magicnumber = 0;
297 const ssize_t result_mn = SafeRead(fd_lockfile_rw, &lockfile_magicnumber, sizeof(lockfile_magicnumber));
298 const ssize_t result = SafeRead(fd_lockfile_rw, &new_cachemgr_pid, sizeof(new_cachemgr_pid));
299 close(fd_lockfile_rw);
300
301 if ((lockfile_magicnumber != kLockFileMagicNumber) || (result < 0) || (result_mn < 0)
302 || (static_cast<size_t>(result) < sizeof(new_cachemgr_pid))) {
303 if (result != 0) {
304 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
305 "could not read cache manager pid from lockfile");
306 UnlockFile(fd_lockfile);
307 delete quota_mgr;
308 return NULL;
309 } else {
310 // support reload from old versions of the cache manager
311 // lock file is empty in this case, try a plain ReadHalfPipe to get pid
312 quota_mgr->SetCacheMgrPid(quota_mgr->GetPid());
313 }
314 } else {
315 quota_mgr->SetCacheMgrPid(new_cachemgr_pid);
316 }
317
318
319 LogCvmfs(kLogQuota, kLogDebug, "connected to existing cache manager pipe");
320 quota_mgr->initialized_ = true;
321 Nonblock2Block(quota_mgr->pipe_lru_[1]);
322 UnlockFile(fd_lockfile);
323 quota_mgr->GetLimits(&quota_mgr->limit_, &quota_mgr->cleanup_threshold_);
324 LogCvmfs(kLogQuota, kLogDebug,
325 "received limit %" PRIu64 ", threshold %" PRIu64,
326 quota_mgr->limit_, quota_mgr->cleanup_threshold_);
327 if (FileExists(workspace_dir + "/cachemgr.protocol")) {
328 quota_mgr->protocol_revision_ = quota_mgr->GetProtocolRevision();
329 LogCvmfs(kLogQuota, kLogDebug, "connected protocol revision %u",
330 quota_mgr->protocol_revision_);
331 } else {
332 LogCvmfs(kLogQuota, kLogDebug, "connected to ancient cache manager");
333 }
334 return quota_mgr;
335 }
336 1 const int connect_error = errno;
337
338 // Lock file: let existing cache manager finish first
339
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 const int fd_lockfile_fifo = LockFile(workspace_dir + "/lock_cachemgr.fifo");
340
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (fd_lockfile_fifo < 0) {
341 LogCvmfs(kLogQuota, kLogDebug, "could not open lock file %s (%d)",
342 (workspace_dir + "/lock_cachemgr.fifo").c_str(), errno);
343 UnlockFile(fd_lockfile);
344 delete quota_mgr;
345 return NULL;
346 }
347
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 UnlockFile(fd_lockfile_fifo);
348
349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (connect_error == ENXIO) {
350 LogCvmfs(kLogQuota, kLogDebug, "left-over FIFO found, unlinking");
351 unlink(fifo_path.c_str());
352 }
353
354 // Creating a new FIFO for the cache manager (to be bound later)
355 1 int retval = mkfifo(fifo_path.c_str(), 0600);
356
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (retval != 0) {
357 LogCvmfs(kLogQuota, kLogDebug, "failed to create cache manager FIFO (%d)",
358 errno);
359 UnlockFile(fd_lockfile);
360 delete quota_mgr;
361 return NULL;
362 }
363
364 // Create new cache manager
365 int pipe_boot[2];
366 int pipe_handshake[2];
367
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 MakePipe(pipe_boot);
368
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 MakePipe(pipe_handshake);
369
370 1 vector<string> command_line;
371
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 command_line.push_back(exe_path);
372
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 command_line.push_back("__cachemgr__");
373
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 command_line.push_back(cache_workspace);
374
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 command_line.push_back(StringifyInt(pipe_boot[1]));
375
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 command_line.push_back(StringifyInt(pipe_handshake[0]));
376
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 command_line.push_back(StringifyInt(limit));
377
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 command_line.push_back(StringifyInt(cleanup_threshold));
378 // do not propagate foreground in order to reliably get pid from exec
379 // instead, daemonize right here
380
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 command_line.push_back(StringifyInt(true)); //foreground
381
3/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
1 command_line.push_back(StringifyInt(GetLogSyslogLevel()));
382
3/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
1 command_line.push_back(StringifyInt(GetLogSyslogFacility()));
383
5/14
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
1 command_line.push_back(GetLogDebugFile() + ":" + GetLogMicroSyslog());
384
385 1 set<int> preserve_filedes;
386
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 preserve_filedes.insert(0);
387
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 preserve_filedes.insert(1);
388
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 preserve_filedes.insert(2);
389
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 preserve_filedes.insert(pipe_boot[1]);
390
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 preserve_filedes.insert(pipe_handshake[0]);
391
392
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (foreground) {
393
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 retval = ManagedExec(command_line, preserve_filedes, map<int, int>(),
394 /*drop_credentials*/ false,
395 /*clear_env*/ false,
396 /*double_fork*/ true,
397 &new_cachemgr_pid);
398 } else {
399 retval = ExecAsDaemon(command_line, &new_cachemgr_pid);
400 }
401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!retval) {
402 UnlockFile(fd_lockfile);
403 ClosePipe(pipe_boot);
404 ClosePipe(pipe_handshake);
405 delete quota_mgr;
406 LogCvmfs(kLogQuota, kLogDebug, "failed to start cache manager");
407 return NULL;
408 }
409
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "new cache manager pid: %d", new_cachemgr_pid);
410 1 quota_mgr->SetCacheMgrPid(new_cachemgr_pid);
411
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 const int fd_lockfile_rw = open((workspace_dir + "/lock_cachemgr").c_str(), O_RDWR | O_TRUNC, 0600);
412 1 const unsigned magic_number = PosixQuotaManager::kLockFileMagicNumber;
413
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 const bool result_mn = SafeWrite(fd_lockfile_rw, &magic_number, sizeof(magic_number));
414
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 const bool result = SafeWrite(fd_lockfile_rw, &new_cachemgr_pid, sizeof(new_cachemgr_pid));
415
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!result || !result_mn) {
416 PANIC(kLogSyslogErr, "could not write cache manager pid to lockfile");
417 }
418
419
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 close(fd_lockfile_rw);
420 // Wait for cache manager to be ready
421
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 close(pipe_boot[1]);
422
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 close(pipe_handshake[0]);
423 char buf;
424
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if (read(pipe_boot[0], &buf, 1) != 1) {
425
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 UnlockFile(fd_lockfile);
426
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 close(pipe_boot[0]);
427
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 close(pipe_handshake[1]);
428
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete quota_mgr;
429
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
430 "cache manager did not start");
431 1 return NULL;
432 }
433 close(pipe_boot[0]);
434
435 // Connect write end
436 quota_mgr->pipe_lru_[1] = open(fifo_path.c_str(), O_WRONLY | O_NONBLOCK);
437 if (quota_mgr->pipe_lru_[1] < 0) {
438 LogCvmfs(kLogQuota, kLogDebug,
439 "failed to connect to newly created FIFO (%d)", errno);
440 close(pipe_handshake[1]);
441 UnlockFile(fd_lockfile);
442 delete quota_mgr;
443 return NULL;
444 }
445
446 // Finalize handshake
447 buf = 'C';
448 if (write(pipe_handshake[1], &buf, 1) != 1) {
449 UnlockFile(fd_lockfile);
450 close(pipe_handshake[1]);
451 LogCvmfs(kLogQuota, kLogDebug, "could not finalize handshake");
452 delete quota_mgr;
453 return NULL;
454 }
455 close(pipe_handshake[1]);
456
457 Nonblock2Block(quota_mgr->pipe_lru_[1]);
458 LogCvmfs(kLogQuota, kLogDebug, "connected to a new cache manager");
459 quota_mgr->protocol_revision_ = kProtocolRevision;
460
461 UnlockFile(fd_lockfile);
462
463 quota_mgr->initialized_ = true;
464 quota_mgr->GetLimits(&quota_mgr->limit_, &quota_mgr->cleanup_threshold_);
465 LogCvmfs(kLogQuota, kLogDebug, "received limit %" PRIu64 ", "
466 "threshold %" PRIu64,
467 quota_mgr->limit_, quota_mgr->cleanup_threshold_);
468 return quota_mgr;
469 2 }
470
471
472 10 bool PosixQuotaManager::DoCleanup(const uint64_t leave_size) {
473
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 if (gauge_ <= leave_size)
474 2 return true;
475
476 // TODO(jblomer) transaction
477
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 LogCvmfs(kLogQuota, kLogSyslog | kLogDebug,
478 "clean up cache until at most %lu KB is used", leave_size/1024);
479
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 LogCvmfs(kLogQuota, kLogDebug, "gauge %" PRIu64, gauge_);
480
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 cleanup_recorder_.Tick();
481
482 bool result;
483 8 vector<string> trash;
484
485 // Note that volatile files start counting from the smallest int64 number:
486 // the absolute sequence number with the first bit set in two's complement.
487 // So -1 can be a marker that will never appear in the database.
488 8 int64_t max_acseq = -1;
489 do {
490
1/2
✓ Branch 1 taken 58 times.
✗ Branch 2 not taken.
58 sqlite3_reset(stmt_lru_);
491
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 50 times.
✓ Branch 3 taken 58 times.
✗ Branch 4 not taken.
66 sqlite3_bind_int64(stmt_lru_, 1, (max_acseq == -1) ?
492 8 std::numeric_limits<int64_t>::min() : (max_acseq + 1));
493
494 58 std::vector<EvictCandidate> candidates;
495
1/2
✓ Branch 1 taken 58 times.
✗ Branch 2 not taken.
58 candidates.reserve(kEvictBatchSize);
496 58 string hash_str;
497 58 unsigned i = 0;
498
3/4
✓ Branch 1 taken 52068 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 52010 times.
✓ Branch 4 taken 58 times.
52068 while (sqlite3_step(stmt_lru_) == SQLITE_ROW) {
499 hash_str = reinterpret_cast<const char *>(
500
2/4
✓ Branch 1 taken 52010 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52010 times.
✗ Branch 5 not taken.
52010 sqlite3_column_text(stmt_lru_, 0));
501
1/2
✓ Branch 2 taken 52010 times.
✗ Branch 3 not taken.
52010 LogCvmfs(kLogQuota, kLogDebug, "add %s to candidates for eviction",
502 hash_str.c_str());
503
1/2
✓ Branch 2 taken 52010 times.
✗ Branch 3 not taken.
52010 candidates.push_back(EvictCandidate(
504
1/2
✓ Branch 1 taken 52010 times.
✗ Branch 2 not taken.
52010 shash::MkFromHexPtr(shash::HexPtr(hash_str)),
505 52010 sqlite3_column_int64(stmt_lru_, 1),
506
2/4
✓ Branch 1 taken 52010 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52010 times.
✗ Branch 5 not taken.
52010 sqlite3_column_int64(stmt_lru_, 2)));
507 52010 i++;
508 }
509
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 57 times.
58 if (candidates.empty()) {
510
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "no more entries to evict");
511 1 break;
512 }
513
514 57 const unsigned N = candidates.size();
515
2/2
✓ Branch 0 taken 50009 times.
✓ Branch 1 taken 50 times.
50059 for (i = 0; i < N; ++i) {
516 // That's a critical condition. We must not delete a not yet inserted
517 // pinned file as it is already reserved (but will be inserted later).
518 // Instead, set the pin bit in the db to not run into an endless loop
519
3/4
✓ Branch 3 taken 50009 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 50008 times.
50009 if (pinned_chunks_.find(candidates[i].hash) != pinned_chunks_.end()) {
520
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 hash_str = candidates[i].hash.ToString();
521
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "skip %s for eviction",
522 hash_str.c_str());
523
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 sqlite3_bind_text(stmt_block_, 1, &hash_str[0], hash_str.length(),
524 SQLITE_STATIC);
525
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 result = (sqlite3_step(stmt_block_) == SQLITE_DONE);
526
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_reset(stmt_block_);
527
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 assert(result);
528 1 continue;
529 }
530
531
3/6
✓ Branch 1 taken 50008 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 50008 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 50008 times.
✗ Branch 8 not taken.
50008 trash.push_back(cache_dir_ + "/" +
532
1/2
✓ Branch 2 taken 50008 times.
✗ Branch 3 not taken.
100016 candidates[i].hash.MakePathWithoutSuffix());
533 50008 gauge_ -= candidates[i].size;
534 50008 max_acseq = candidates[i].acseq;
535
1/2
✓ Branch 2 taken 50008 times.
✗ Branch 3 not taken.
50008 LogCvmfs(kLogQuota, kLogDebug, "lru cleanup %s, new gauge %" PRIu64,
536
1/2
✓ Branch 2 taken 50008 times.
✗ Branch 3 not taken.
100016 candidates[i].hash.ToString().c_str(), gauge_);
537
538
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 50001 times.
50008 if (gauge_ <= leave_size)
539 7 break;
540 }
541
6/6
✓ Branch 1 taken 57 times.
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 57 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 50 times.
✓ Branch 7 taken 7 times.
116 } while (gauge_ > leave_size);
542
543
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (max_acseq != -1) {
544
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 sqlite3_bind_int64(stmt_rm_batch_, 1, max_acseq);
545
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 result = (sqlite3_step(stmt_rm_batch_) == SQLITE_DONE);
546
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 assert(result);
547
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 sqlite3_reset(stmt_rm_batch_);
548
549
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 result = (sqlite3_step(stmt_unblock_) == SQLITE_DONE);
550
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 sqlite3_reset(stmt_unblock_);
551
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 assert(result);
552 }
553
554
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 if (!EmptyTrash(trash))
555 return false;
556
557
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (gauge_ > leave_size) {
558
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
559 "request to clean until %" PRIu64 ", "
560 "but effective gauge is %" PRIu64, leave_size, gauge_);
561 1 return false;
562 }
563 7 return true;
564 8 }
565
566 8 bool PosixQuotaManager::EmptyTrash(const std::vector<std::string> &trash) {
567
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (trash.empty())
568 return true;
569
570
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (async_delete_) {
571 // Double fork avoids zombie, forked removal process must not flush file
572 // buffers
573 pid_t pid;
574 int statloc;
575
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if ((pid = fork()) == 0) {
576 // TODO(jblomer): eviciting files in the cache should perhaps become a
577 // thread. This would also allow to block the chunks and prevent the
578 // race with re-insertion. Then again, a thread can block umount.
579 #ifndef DEBUGMSG
580 CloseAllFildes(std::set<int>());
581 #endif
582 if (fork() == 0) {
583 for (unsigned i = 0, iEnd = trash.size(); i < iEnd; ++i) {
584 LogCvmfs(kLogQuota, kLogDebug, "unlink %s", trash[i].c_str());
585 unlink(trash[i].c_str());
586 }
587 _exit(0);
588 }
589 _exit(0);
590 } else {
591
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (pid > 0)
592
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 waitpid(pid, &statloc, 0);
593 else
594 return false;
595 }
596 } else { // !async_delete_
597
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
5 for (unsigned i = 0, iEnd = trash.size(); i < iEnd; ++i) {
598 3 LogCvmfs(kLogQuota, kLogDebug, "unlink %s", trash[i].c_str());
599 3 unlink(trash[i].c_str());
600 }
601 }
602 8 return true;
603 }
604
605
606 100042 void PosixQuotaManager::DoInsert(
607 const shash::Any &hash,
608 const uint64_t size,
609 const string &description,
610 const CommandType command_type)
611 {
612
1/2
✓ Branch 1 taken 100042 times.
✗ Branch 2 not taken.
100042 const string hash_str = hash.ToString();
613
1/2
✓ Branch 3 taken 100042 times.
✗ Branch 4 not taken.
100042 LogCvmfs(kLogQuota, kLogDebug, "insert into lru %s, path %s, method %d",
614 hash_str.c_str(), description.c_str(), command_type);
615 100042 const unsigned desc_length = (description.length() > kMaxDescription) ?
616
1/2
✓ Branch 0 taken 100042 times.
✗ Branch 1 not taken.
100042 kMaxDescription : description.length();
617
618 LruCommand *cmd =
619 100042 reinterpret_cast<LruCommand *>(alloca(sizeof(LruCommand) + desc_length));
620 100042 new (cmd) LruCommand;
621 100042 cmd->command_type = command_type;
622 100042 cmd->SetSize(size);
623
1/2
✓ Branch 1 taken 100042 times.
✗ Branch 2 not taken.
100042 cmd->StoreHash(hash);
624 100042 cmd->desc_length = desc_length;
625 200084 memcpy(reinterpret_cast<char *>(cmd)+sizeof(LruCommand),
626 100042 &description[0], desc_length);
627
1/2
✓ Branch 1 taken 100042 times.
✗ Branch 2 not taken.
100042 WritePipe(pipe_lru_[1], cmd, sizeof(LruCommand) + desc_length);
628 100042 }
629
630
631 35 vector<string> PosixQuotaManager::DoList(const CommandType list_command) {
632 35 vector<string> result;
633
634 int pipe_list[2];
635
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 MakeReturnPipe(pipe_list);
636 char description_buffer[kMaxDescription];
637
638 35 LruCommand cmd;
639 35 cmd.command_type = list_command;
640 35 cmd.return_pipe = pipe_list[1];
641
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
642
643 int length;
644 do {
645
1/2
✓ Branch 1 taken 100074 times.
✗ Branch 2 not taken.
100074 ManagedReadHalfPipe(pipe_list[0], &length, sizeof(length));
646
2/2
✓ Branch 0 taken 100039 times.
✓ Branch 1 taken 35 times.
100074 if (length > 0) {
647
1/2
✓ Branch 1 taken 100039 times.
✗ Branch 2 not taken.
100039 ReadPipe(pipe_list[0], description_buffer, length);
648
2/4
✓ Branch 2 taken 100039 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 100039 times.
✗ Branch 6 not taken.
100039 result.push_back(string(description_buffer, length));
649 }
650
2/2
✓ Branch 0 taken 100039 times.
✓ Branch 1 taken 35 times.
100074 } while (length >= 0);
651
652
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 CloseReturnPipe(pipe_list);
653 70 return result;
654 }
655
656
657 34 uint64_t PosixQuotaManager::GetCapacity() {
658
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 if (limit_ != (uint64_t)(-1))
659 34 return limit_;
660
661 // Unrestricted cache, look at free space on cache dir fs
662 struct statfs info;
663 if (statfs(".", &info) == 0) {
664 return info.f_bavail * info.f_bsize;
665 } else {
666 LogCvmfs(kLogQuota, kLogSyslogErr | kLogDebug,
667 "failed to query file system info of cache (%d)", errno);
668 return limit_;
669 }
670 }
671
672
673 void PosixQuotaManager::GetLimits(uint64_t *limit, uint64_t *cleanup_threshold)
674 {
675 int pipe_limits[2];
676 MakeReturnPipe(pipe_limits);
677
678 LruCommand cmd;
679 cmd.command_type = kLimits;
680 cmd.return_pipe = pipe_limits[1];
681 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
682 ManagedReadHalfPipe(pipe_limits[0], limit, sizeof(*limit));
683 ReadPipe(pipe_limits[0], cleanup_threshold, sizeof(*cleanup_threshold));
684 CloseReturnPipe(pipe_limits);
685 }
686
687
688 /**
689 * Since we only cleanup until cleanup_threshold, we can only add
690 * files smaller than limit-cleanup_threshold.
691 */
692 7 uint64_t PosixQuotaManager::GetMaxFileSize() {
693 7 return limit_ - cleanup_threshold_;
694 }
695
696
697 1 pid_t PosixQuotaManager::GetPid() {
698
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!shared_ || !spawned_) {
699 1 return getpid();
700 }
701 if (cachemgr_pid_) {
702 return cachemgr_pid_;
703 }
704
705 pid_t result;
706 int pipe_pid[2];
707 MakeReturnPipe(pipe_pid);
708
709 LruCommand cmd;
710 cmd.command_type = kPid;
711 cmd.return_pipe = pipe_pid[1];
712 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
713 ReadHalfPipe(pipe_pid[0], &result, sizeof(result));
714 CloseReturnPipe(pipe_pid);
715 return result;
716 }
717
718
719 1 uint32_t PosixQuotaManager::GetProtocolRevision() {
720 int pipe_revision[2];
721
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 MakeReturnPipe(pipe_revision);
722
723 1 LruCommand cmd;
724 1 cmd.command_type = kGetProtocolRevision;
725 1 cmd.return_pipe = pipe_revision[1];
726
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
727
728 uint32_t revision;
729
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ManagedReadHalfPipe(pipe_revision[0], &revision, sizeof(revision));
730
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 CloseReturnPipe(pipe_revision);
731 1 return revision;
732 }
733
734
735 /**
736 * Queries the shared local hard disk quota manager.
737 */
738 18 void PosixQuotaManager::GetSharedStatus(uint64_t *gauge, uint64_t *pinned) {
739 int pipe_status[2];
740
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 MakeReturnPipe(pipe_status);
741
742 18 LruCommand cmd;
743 18 cmd.command_type = kStatus;
744 18 cmd.return_pipe = pipe_status[1];
745
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
746
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 ManagedReadHalfPipe(pipe_status[0], gauge, sizeof(*gauge));
747
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 ReadPipe(pipe_status[0], pinned, sizeof(*pinned));
748
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 CloseReturnPipe(pipe_status);
749 18 }
750
751 1 bool PosixQuotaManager::SetSharedLimit(uint64_t limit) {
752 int pipe_set_limit[2];
753 bool result;
754
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 MakeReturnPipe(pipe_set_limit);
755
756 1 LruCommand cmd;
757 1 cmd.command_type = kSetLimit;
758 1 cmd.size = limit;
759 1 cmd.return_pipe = pipe_set_limit[1];
760
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
761
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ReadHalfPipe(pipe_set_limit[0], &result, sizeof(result));
762
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 CloseReturnPipe(pipe_set_limit);
763 1 return result;
764 }
765
766
767 1 bool PosixQuotaManager::SetLimit( uint64_t size) {
768
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!spawned_) {
769 limit_ = size;
770 cleanup_threshold_ = size/2;
771 LogCvmfs(kLogQuota, kLogDebug | kLogSyslog, "Quota limit set to %lu / threshold %lu", limit_, cleanup_threshold_ );
772 return true;
773 }
774 1 return SetSharedLimit(size);
775 }
776
777 82 uint64_t PosixQuotaManager::GetSize() {
778
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 16 times.
82 if (!spawned_) return gauge_;
779 uint64_t gauge, size_pinned;
780
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 GetSharedStatus(&gauge, &size_pinned);
781 16 return gauge;
782 }
783
784
785 2 uint64_t PosixQuotaManager::GetSizePinned() {
786
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!spawned_) return pinned_;
787 uint64_t gauge, size_pinned;
788
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 GetSharedStatus(&gauge, &size_pinned);
789 2 return size_pinned;
790 }
791
792
793 4 uint64_t PosixQuotaManager::GetCleanupRate(uint64_t period_s) {
794
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (!spawned_ || (protocol_revision_ < 2)) return 0;
795 uint64_t cleanup_rate;
796
797 int pipe_cleanup_rate[2];
798
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 MakeReturnPipe(pipe_cleanup_rate);
799 4 LruCommand cmd;
800 4 cmd.command_type = kCleanupRate;
801 4 cmd.size = period_s;
802 4 cmd.return_pipe = pipe_cleanup_rate[1];
803
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
804
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ManagedReadHalfPipe(pipe_cleanup_rate[0], &cleanup_rate, sizeof(cleanup_rate));
805
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 CloseReturnPipe(pipe_cleanup_rate);
806
807 4 return cleanup_rate;
808 }
809
810
811 92 bool PosixQuotaManager::InitDatabase(const bool rebuild_database) {
812 92 string sql;
813 sqlite3_stmt *stmt;
814
815
2/4
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 92 times.
✗ Branch 5 not taken.
92 fd_lock_cachedb_ = LockFile(workspace_dir_ + "/lock_cachedb");
816
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 91 times.
92 if (fd_lock_cachedb_ < 0) {
817
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "failed to create cachedb lock");
818 1 return false;
819 }
820
821 91 bool retry = false;
822
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 const string db_file = cache_dir_ + "/cachedb";
823
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 5 times.
91 if (rebuild_database) {
824
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 LogCvmfs(kLogQuota, kLogDebug, "rebuild database, unlinking existing (%s)",
825 db_file.c_str());
826 5 unlink(db_file.c_str());
827
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 unlink((db_file + "-journal").c_str());
828 }
829
830 86 init_recover:
831
1/2
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
91 int err = sqlite3_open(db_file.c_str(), &database_);
832
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 if (err != SQLITE_OK) {
833 LogCvmfs(kLogQuota, kLogDebug, "could not open cache database (%d)", err);
834 goto init_database_fail;
835 }
836 // TODO(reneme): make this a `QuotaDatabase : public sqlite::Database`
837 sql = "PRAGMA synchronous=0; PRAGMA locking_mode=EXCLUSIVE; "
838 "PRAGMA auto_vacuum=1; "
839 "CREATE TABLE IF NOT EXISTS cache_catalog (sha1 TEXT, size INTEGER, "
840 " acseq INTEGER, path TEXT, type INTEGER, pinned INTEGER, "
841 "CONSTRAINT pk_cache_catalog PRIMARY KEY (sha1)); "
842 "CREATE UNIQUE INDEX IF NOT EXISTS idx_cache_catalog_acseq "
843 " ON cache_catalog (acseq); "
844 "CREATE TEMP TABLE fscache (sha1 TEXT, size INTEGER, actime INTEGER, "
845 "CONSTRAINT pk_fscache PRIMARY KEY (sha1)); "
846 "CREATE INDEX idx_fscache_actime ON fscache (actime); "
847 "CREATE TABLE IF NOT EXISTS properties (key TEXT, value TEXT, "
848
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 " CONSTRAINT pk_properties PRIMARY KEY(key));";
849
1/2
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
91 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
850
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 if (err != SQLITE_OK) {
851 if (!retry) {
852 retry = true;
853 sqlite3_close(database_);
854 unlink(db_file.c_str());
855 unlink((db_file + "-journal").c_str());
856 LogCvmfs(kLogQuota, kLogSyslogWarn,
857 "LRU database corrupted, re-building");
858 goto init_recover;
859 }
860 LogCvmfs(kLogQuota, kLogDebug, "could not init cache database (failed: %s)",
861 sql.c_str());
862 goto init_database_fail;
863 }
864
865 // If this an old cache catalog,
866 // add and initialize new columns to cache_catalog
867 sql = "ALTER TABLE cache_catalog ADD type INTEGER; "
868
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 "ALTER TABLE cache_catalog ADD pinned INTEGER";
869
1/2
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
91 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
870
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 if (err == SQLITE_OK) {
871 sql = "UPDATE cache_catalog SET type=" + StringifyInt(kFileRegular) + ";";
872 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
873 if (err != SQLITE_OK) {
874 LogCvmfs(kLogQuota, kLogDebug,
875 "could not init cache database (failed: %s)", sql.c_str());
876 goto init_database_fail;
877 }
878 }
879
880 // Set pinned back
881
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 sql = "UPDATE cache_catalog SET pinned=0;";
882
1/2
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
91 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
883
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 if (err != SQLITE_OK) {
884 LogCvmfs(kLogQuota, kLogDebug, "could not init cache database (failed: %s)",
885 sql.c_str());
886 goto init_database_fail;
887 }
888
889 // Set schema version
890 sql = "INSERT OR REPLACE INTO properties (key, value) "
891
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 "VALUES ('schema', '1.0')";
892
1/2
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
91 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
893
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 if (err != SQLITE_OK) {
894 LogCvmfs(kLogQuota, kLogDebug, "could not init cache database (failed: %s)",
895 sql.c_str());
896 goto init_database_fail;
897 }
898
899 // If cache catalog is empty, recreate from file system
900
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 sql = "SELECT count(*) FROM cache_catalog;";
901
1/2
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
91 sqlite3_prepare_v2(database_, sql.c_str(), -1, &stmt, NULL);
902
2/4
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 91 times.
✗ Branch 4 not taken.
91 if (sqlite3_step(stmt) == SQLITE_ROW) {
903
6/8
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 87 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 87 times.
✓ Branch 8 taken 4 times.
91 if ((sqlite3_column_int64(stmt, 0)) == 0 || rebuild_database) {
904
1/2
✓ Branch 1 taken 87 times.
✗ Branch 2 not taken.
87 LogCvmfs(kLogCvmfs, kLogDebug,
905 "CernVM-FS: building lru cache database...");
906
3/4
✓ Branch 1 taken 87 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 84 times.
87 if (!RebuildDatabase()) {
907
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 LogCvmfs(kLogQuota, kLogDebug,
908 "could not build cache database from file system");
909
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 sqlite3_finalize(stmt);
910 3 goto init_database_fail;
911 }
912 }
913
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_finalize(stmt);
914 } else {
915 LogCvmfs(kLogQuota, kLogDebug, "could not select on cache catalog");
916 sqlite3_finalize(stmt);
917 goto init_database_fail;
918 }
919
920 // How many bytes do we already have in cache?
921
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sql = "SELECT sum(size) FROM cache_catalog;";
922
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 sqlite3_prepare_v2(database_, sql.c_str(), -1, &stmt, NULL);
923
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
88 if (sqlite3_step(stmt) == SQLITE_ROW) {
924
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 gauge_ = sqlite3_column_int64(stmt, 0);
925 } else {
926 LogCvmfs(kLogQuota, kLogDebug, "could not determine cache size");
927 sqlite3_finalize(stmt);
928 goto init_database_fail;
929 }
930
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_finalize(stmt);
931
932 // Highest seq-no?
933
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sql = "SELECT coalesce(max(acseq & (~(1<<63))), 0) FROM cache_catalog;";
934
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 sqlite3_prepare_v2(database_, sql.c_str(), -1, &stmt, NULL);
935
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
88 if (sqlite3_step(stmt) == SQLITE_ROW) {
936
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 seq_ = sqlite3_column_int64(stmt, 0)+1;
937 } else {
938 LogCvmfs(kLogQuota, kLogDebug, "could not determine highest seq-no");
939 sqlite3_finalize(stmt);
940 goto init_database_fail;
941 }
942
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_finalize(stmt);
943
944 // Prepare touch, new, remove statements
945
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_,
946 "UPDATE cache_catalog SET acseq=:seq | (acseq&(1<<63)) "
947 "WHERE sha1=:sha1;", -1, &stmt_touch_, NULL);
948
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_, "UPDATE cache_catalog SET pinned=0 "
949 "WHERE sha1=:sha1;", -1, &stmt_unpin_, NULL);
950
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_, "UPDATE cache_catalog SET pinned=2 "
951 "WHERE sha1=:sha1;", -1, &stmt_block_, NULL);
952
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_, "UPDATE cache_catalog SET pinned=1 "
953 "WHERE pinned=2;", -1, &stmt_unblock_, NULL);
954
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_,
955 "INSERT OR REPLACE INTO cache_catalog "
956 "(sha1, size, acseq, path, type, pinned) "
957 "VALUES (:sha1, :s, :seq, :p, :t, :pin);",
958 -1, &stmt_new_, NULL);
959
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_,
960 "SELECT size, pinned FROM cache_catalog WHERE sha1=:sha1;",
961 -1, &stmt_size_, NULL);
962
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_, "DELETE FROM cache_catalog WHERE sha1=:sha1;",
963 -1, &stmt_rm_, NULL);
964
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_,
965 "DELETE FROM cache_catalog WHERE acseq<=:a AND pinned<>2;",
966 -1, &stmt_rm_batch_, NULL);
967
2/4
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 88 times.
✗ Branch 7 not taken.
264 sqlite3_prepare_v2(database_, (std::string(
968 "SELECT sha1, size, acseq FROM cache_catalog "
969 "WHERE pinned<>2 AND acseq>=:a "
970 "ORDER BY acseq ASC "
971
3/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 88 times.
✗ Branch 8 not taken.
352 "LIMIT ") + StringifyInt(kEvictBatchSize) + ";").c_str(),
972 -1, &stmt_lru_, NULL);
973
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 sqlite3_prepare_v2(database_,
974
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 ("SELECT path FROM cache_catalog WHERE type=" +
975
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
264 StringifyInt(kFileRegular) +
976 ";").c_str(), -1, &stmt_list_, NULL);
977
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_,
978 "SELECT path FROM cache_catalog WHERE pinned<>0;",
979 -1, &stmt_list_pinned_, NULL);
980
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 sqlite3_prepare_v2(database_,
981 "SELECT path FROM cache_catalog WHERE acseq < 0;",
982 -1, &stmt_list_volatile_, NULL);
983
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 sqlite3_prepare_v2(database_,
984
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 ("SELECT path FROM cache_catalog WHERE type=" +
985
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
264 StringifyInt(kFileCatalog) +
986 ";").c_str(), -1, &stmt_list_catalogs_, NULL);
987 88 return true;
988
989 3 init_database_fail:
990
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 sqlite3_close(database_);
991 3 database_ = NULL;
992
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 UnlockFile(fd_lock_cachedb_);
993 3 return false;
994 92 }
995
996
997 /**
998 * Inserts a new file into cache catalog. This file gets a new,
999 * highest sequence number. Does cache cleanup if necessary.
1000 */
1001 100025 void PosixQuotaManager::Insert(
1002 const shash::Any &any_hash,
1003 const uint64_t size,
1004 const string &description)
1005 {
1006 100025 DoInsert(any_hash, size, description, kInsert);
1007 100025 }
1008
1009
1010 /**
1011 * Inserts a new file into cache catalog. This file is marked as volatile
1012 * and gets a new highest sequence number with the first bit set. Cache cleanup
1013 * treats these files with priority.
1014 */
1015 4 void PosixQuotaManager::InsertVolatile(
1016 const shash::Any &any_hash,
1017 const uint64_t size,
1018 const string &description)
1019 {
1020 4 DoInsert(any_hash, size, description, kInsertVolatile);
1021 4 }
1022
1023
1024 /**
1025 * Lists all path names from the cache db.
1026 */
1027 21 vector<string> PosixQuotaManager::List() {
1028 21 return DoList(kList);
1029 }
1030
1031
1032 /**
1033 * Lists all pinned files from the cache db.
1034 */
1035 8 vector<string> PosixQuotaManager::ListPinned() {
1036 8 return DoList(kListPinned);
1037 }
1038
1039
1040 /**
1041 * Lists all sqlite catalog files from the cache db.
1042 */
1043 3 vector<string> PosixQuotaManager::ListCatalogs() {
1044 3 return DoList(kListCatalogs);
1045 }
1046
1047
1048 /**
1049 * Lists only files flagged as volatile (priority removal)
1050 */
1051 3 vector<string> PosixQuotaManager::ListVolatile() {
1052 3 return DoList(kListVolatile);
1053 }
1054
1055
1056 /**
1057 * Entry point for the shared cache manager process
1058 */
1059 int PosixQuotaManager::MainCacheManager(int argc, char **argv) {
1060
1061 LogCvmfs(kLogQuota, kLogDebug, "starting quota manager");
1062 int retval;
1063
1064 PosixQuotaManager shared_manager(0, 0, "");
1065 shared_manager.shared_ = true;
1066 shared_manager.spawned_ = true;
1067 shared_manager.pinned_ = 0;
1068
1069 // Process command line arguments
1070 ParseDirectories(string(argv[2]),
1071 &shared_manager.cache_dir_,
1072 &shared_manager.workspace_dir_);
1073 int pipe_boot = String2Int64(argv[3]);
1074 int pipe_handshake = String2Int64(argv[4]);
1075 shared_manager.limit_ = String2Int64(argv[5]);
1076 shared_manager.cleanup_threshold_ = String2Int64(argv[6]);
1077 int foreground = String2Int64(argv[7]);
1078 int syslog_level = String2Int64(argv[8]);
1079 int syslog_facility = String2Int64(argv[9]);
1080 vector<string> logfiles = SplitString(argv[10], ':');
1081
1082 SetLogSyslogLevel(syslog_level);
1083 SetLogSyslogFacility(syslog_facility);
1084 if ((logfiles.size() > 0) && (logfiles[0] != ""))
1085 SetLogDebugFile(logfiles[0] + ".cachemgr");
1086 if (logfiles.size() > 1)
1087 SetLogMicroSyslog(logfiles[1]);
1088
1089 if (!foreground)
1090 Daemonize();
1091
1092 UniquePtr<Watchdog> watchdog(Watchdog::Create(NULL));
1093 assert(watchdog.IsValid());
1094 watchdog->Spawn("./stacktrace.cachemgr");
1095
1096 // Initialize pipe, open non-blocking as cvmfs is not yet connected
1097 const int fd_lockfile_fifo =
1098 LockFile(shared_manager.workspace_dir_ + "/lock_cachemgr.fifo");
1099 if (fd_lockfile_fifo < 0) {
1100 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr, "could not open lock file "
1101 "%s (%d)",
1102 (shared_manager.workspace_dir_ + "/lock_cachemgr.fifo").c_str(),
1103 errno);
1104 return 1;
1105 }
1106 const string crash_guard = shared_manager.cache_dir_ + "/cachemgr.running";
1107 const bool rebuild = FileExists(crash_guard);
1108 retval = open(crash_guard.c_str(), O_RDONLY | O_CREAT, 0600);
1109 if (retval < 0) {
1110 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1111 "failed to create shared cache manager crash guard");
1112 UnlockFile(fd_lockfile_fifo);
1113 return 1;
1114 }
1115 close(retval);
1116
1117 // Redirect SQlite temp directory to cache (global variable)
1118 const string tmp_dir = shared_manager.workspace_dir_;
1119 sqlite3_temp_directory =
1120 static_cast<char *>(sqlite3_malloc(tmp_dir.length() + 1));
1121 snprintf(sqlite3_temp_directory, tmp_dir.length() + 1, "%s", tmp_dir.c_str());
1122
1123 // Cleanup leftover named pipes
1124 shared_manager.CleanupPipes();
1125
1126 if (!shared_manager.InitDatabase(rebuild)) {
1127 UnlockFile(fd_lockfile_fifo);
1128 return 1;
1129 }
1130 shared_manager.CheckFreeSpace();
1131
1132 // Save protocol revision to file. If the file is not found, it indicates
1133 // to the client that the cache manager is from times before the protocol
1134 // was versioned.
1135 const string protocol_revision_path =
1136 shared_manager.workspace_dir_ + "/cachemgr.protocol";
1137 retval = open(protocol_revision_path.c_str(), O_WRONLY | O_CREAT, 0600);
1138 if (retval < 0) {
1139 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1140 "failed to open protocol revision file (%d)", errno);
1141 UnlockFile(fd_lockfile_fifo);
1142 return 1;
1143 }
1144 const string revision = StringifyInt(kProtocolRevision);
1145 int written = write(retval, revision.data(), revision.length());
1146 close(retval);
1147 if ((written < 0) || static_cast<unsigned>(written) != revision.length()) {
1148 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1149 "failed to write protocol revision (%d)", errno);
1150 UnlockFile(fd_lockfile_fifo);
1151 return 1;
1152 }
1153
1154 const string fifo_path = shared_manager.workspace_dir_ + "/cachemgr";
1155 shared_manager.pipe_lru_[0] = open(fifo_path.c_str(), O_RDONLY | O_NONBLOCK);
1156 if (shared_manager.pipe_lru_[0] < 0) {
1157 LogCvmfs(kLogQuota, kLogDebug, "failed to listen on FIFO %s (%d)",
1158 fifo_path.c_str(), errno);
1159 UnlockFile(fd_lockfile_fifo);
1160 return 1;
1161 }
1162 Nonblock2Block(shared_manager.pipe_lru_[0]);
1163 LogCvmfs(kLogQuota, kLogDebug, "shared cache manager listening");
1164
1165 char buf = 'C';
1166 WritePipe(pipe_boot, &buf, 1);
1167 close(pipe_boot);
1168
1169 ReadPipe(pipe_handshake, &buf, 1);
1170 close(pipe_handshake);
1171 LogCvmfs(kLogQuota, kLogDebug, "shared cache manager handshake done");
1172
1173 // Ensure that broken pipes from clients do not kill the cache manager
1174 signal(SIGPIPE, SIG_IGN);
1175 // Don't let Ctrl-C ungracefully kill interactive session
1176 signal(SIGINT, SIG_IGN);
1177
1178 shared_manager.MainCommandServer(&shared_manager);
1179 unlink(fifo_path.c_str());
1180 unlink(protocol_revision_path.c_str());
1181 shared_manager.CloseDatabase();
1182 unlink(crash_guard.c_str());
1183 UnlockFile(fd_lockfile_fifo);
1184
1185 if (sqlite3_temp_directory) {
1186 sqlite3_free(sqlite3_temp_directory);
1187 sqlite3_temp_directory = NULL;
1188 }
1189
1190 return 0;
1191 }
1192
1193
1194 31 void *PosixQuotaManager::MainCommandServer(void *data) {
1195 31 PosixQuotaManager *quota_mgr = static_cast<PosixQuotaManager *>(data);
1196
1197
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 LogCvmfs(kLogQuota, kLogDebug, "starting quota manager");
1198
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 sqlite3_soft_heap_limit(quota_mgr->kSqliteMemPerThread);
1199
1200
2/2
✓ Branch 1 taken 992 times.
✓ Branch 2 taken 31 times.
1023 LruCommand command_buffer[kCommandBufferSize];
1201 char description_buffer[kCommandBufferSize*kMaxDescription];
1202 31 unsigned num_commands = 0;
1203
1204
1/2
✓ Branch 1 taken 150161 times.
✗ Branch 2 not taken.
150161 while (read(quota_mgr->pipe_lru_[0], &command_buffer[num_commands],
1205
2/2
✓ Branch 0 taken 150130 times.
✓ Branch 1 taken 31 times.
150161 sizeof(command_buffer[0])) == sizeof(command_buffer[0]))
1206 {
1207 150130 const CommandType command_type = command_buffer[num_commands].command_type;
1208
1/2
✓ Branch 1 taken 150130 times.
✗ Branch 2 not taken.
150130 LogCvmfs(kLogQuota, kLogDebug, "received command %d", command_type);
1209 150130 const uint64_t size = command_buffer[num_commands].GetSize();
1210
1211 // Inserts and pins come with a description (usually a path)
1212
6/6
✓ Branch 0 taken 50113 times.
✓ Branch 1 taken 100017 times.
✓ Branch 2 taken 50109 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 50107 times.
✓ Branch 5 taken 2 times.
150130 if ((command_type == kInsert) || (command_type == kInsertVolatile) ||
1213
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 50096 times.
50107 (command_type == kPin) || (command_type == kPinRegular))
1214 {
1215 100034 const int desc_length = command_buffer[num_commands].desc_length;
1216 100034 ReadPipe(quota_mgr->pipe_lru_[0],
1217
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 &description_buffer[kMaxDescription*num_commands], desc_length);
1218 }
1219
1220 // The protocol revision is returned immediately
1221
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 150129 times.
150130 if (command_type == kGetProtocolRevision) {
1222 int return_pipe =
1223
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (return_pipe < 0)
1225 continue;
1226
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 WritePipe(return_pipe, &quota_mgr->kProtocolRevision,
1227 sizeof(quota_mgr->kProtocolRevision));
1228
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 quota_mgr->UnbindReturnPipe(return_pipe);
1229 1 continue;
1230 1 }
1231
1232 // The cleanup rate is returned immediately
1233
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 150125 times.
150129 if (command_type == kCleanupRate) {
1234 int return_pipe =
1235
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1236
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (return_pipe < 0)
1237 continue;
1238 4 uint64_t period_s = size; // use the size field to transmit the period
1239
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 uint64_t rate = quota_mgr->cleanup_recorder_.GetNoTicks(period_s);
1240
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 WritePipe(return_pipe, &rate, sizeof(rate));
1241
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 quota_mgr->UnbindReturnPipe(return_pipe);
1242 4 continue;
1243 4 }
1244
1245
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 150124 times.
150125 if (command_type == kSetLimit) {
1246 const int return_pipe =
1247
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (return_pipe < 0)
1249 continue;
1250 1 quota_mgr->limit_ = size; // use the size field to transmit the size
1251 1 quota_mgr->cleanup_threshold_ = size/2;
1252
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug | kLogSyslog, "Quota limit set to %lu / threshold %lu", quota_mgr->limit_, quota_mgr->cleanup_threshold_ );
1253 1 bool ret = true;
1254
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 WritePipe(return_pipe, &ret, sizeof(ret));
1255
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 quota_mgr->UnbindReturnPipe(return_pipe);
1256 1 continue;
1257 1 }
1258
1259 // Reservations are handled immediately and "out of band"
1260
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 150110 times.
150124 if (command_type == kReserve) {
1261 14 bool success = true;
1262 int return_pipe =
1263
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (return_pipe < 0)
1265 continue;
1266
1267
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 const shash::Any hash = command_buffer[num_commands].RetrieveHash();
1268
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 const string hash_str(hash.ToString());
1269
1/2
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 LogCvmfs(kLogQuota, kLogDebug, "reserve %lu bytes for %s",
1270 size, hash_str.c_str());
1271
1272
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 if (quota_mgr->pinned_chunks_.find(hash) ==
1273
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 2 times.
28 quota_mgr->pinned_chunks_.end())
1274 {
1275
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 11 times.
12 if ((quota_mgr->pinned_ + size) > quota_mgr->cleanup_threshold_) {
1276
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogCvmfs(kLogQuota, kLogDebug,
1277 "failed to insert %s (pinned), no space", hash_str.c_str());
1278 1 success = false;
1279 } else {
1280
1/2
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
11 quota_mgr->pinned_chunks_[hash] = size;
1281 11 quota_mgr->pinned_ += size;
1282
1/2
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
11 quota_mgr->CheckHighPinWatermark();
1283 }
1284 }
1285
1286
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 WritePipe(return_pipe, &success, sizeof(success));
1287
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 quota_mgr->UnbindReturnPipe(return_pipe);
1288 14 continue;
1289 14 }
1290
1291 // Back channels are also handled out of band
1292
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 150106 times.
150110 if (command_type == kRegisterBackChannel) {
1293 int return_pipe =
1294
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1295
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (return_pipe < 0)
1296 continue;
1297
1298
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 quota_mgr->UnlinkReturnPipe(command_buffer[num_commands].return_pipe);
1299
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 Block2Nonblock(return_pipe); // back channels are opportunistic
1300
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 shash::Md5 hash;
1301 4 memcpy(hash.digest, command_buffer[num_commands].digest,
1302 4 shash::kDigestSizes[shash::kMd5]);
1303
1304 4 quota_mgr->LockBackChannels();
1305 map<shash::Md5, int>::const_iterator iter =
1306
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 quota_mgr->back_channels_.find(hash);
1307
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if (iter != quota_mgr->back_channels_.end()) {
1308 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
1309 "closing left-over back channel %s", hash.ToString().c_str());
1310 close(iter->second);
1311 }
1312
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 quota_mgr->back_channels_[hash] = return_pipe;
1313 4 quota_mgr->UnlockBackChannels();
1314
1315 4 char success = 'S';
1316
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 WritePipe(return_pipe, &success, sizeof(success));
1317
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 LogCvmfs(kLogQuota, kLogDebug, "register back channel %s on fd %d",
1318
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 hash.ToString().c_str(), return_pipe);
1319
1320 4 continue;
1321 4 }
1322
1323
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 150104 times.
150106 if (command_type == kUnregisterBackChannel) {
1324
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 shash::Md5 hash;
1325 2 memcpy(hash.digest, command_buffer[num_commands].digest,
1326 2 shash::kDigestSizes[shash::kMd5]);
1327
1328 2 quota_mgr->LockBackChannels();
1329 map<shash::Md5, int>::iterator iter =
1330
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 quota_mgr->back_channels_.find(hash);
1331
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (iter != quota_mgr->back_channels_.end()) {
1332
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 LogCvmfs(kLogQuota, kLogDebug,
1333
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 "closing back channel %s", hash.ToString().c_str());
1334
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 close(iter->second);
1335
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 quota_mgr->back_channels_.erase(iter);
1336 } else {
1337 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
1338 "did not find back channel %s", hash.ToString().c_str());
1339 }
1340 2 quota_mgr->UnlockBackChannels();
1341
1342 2 continue;
1343 2 }
1344
1345 // Unpinnings are also handled immediately with respect to the pinned gauge
1346
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 150102 times.
150104 if (command_type == kUnpin) {
1347
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const shash::Any hash = command_buffer[num_commands].RetrieveHash();
1348
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const string hash_str(hash.ToString());
1349
1350 map<shash::Any, uint64_t>::iterator iter =
1351
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 quota_mgr->pinned_chunks_.find(hash);
1352
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (iter != quota_mgr->pinned_chunks_.end()) {
1353 2 quota_mgr->pinned_ -= iter->second;
1354
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 quota_mgr->pinned_chunks_.erase(iter);
1355 // It can happen that files get pinned that were removed from the cache
1356 // (see cache.cc). We fix this at this point, where we remove such
1357 // entries from the cache database.
1358
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
2 if (!FileExists(quota_mgr->cache_dir_ + "/" +
1359
3/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
4 hash.MakePathWithoutSuffix()))
1360 {
1361
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogCvmfs(kLogQuota, kLogDebug,
1362 "remove orphaned pinned hash %s from cache database",
1363 hash_str.c_str());
1364
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 sqlite3_bind_text(quota_mgr->stmt_size_, 1, &hash_str[0],
1365 1 hash_str.length(), SQLITE_STATIC);
1366 int retval;
1367
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if ((retval = sqlite3_step(quota_mgr->stmt_size_)) == SQLITE_ROW) {
1368
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 uint64_t size = sqlite3_column_int64(quota_mgr->stmt_size_, 0);
1369
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 sqlite3_bind_text(quota_mgr->stmt_rm_, 1, &(hash_str[0]),
1370 1 hash_str.length(), SQLITE_STATIC);
1371
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 retval = sqlite3_step(quota_mgr->stmt_rm_);
1372
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if ((retval == SQLITE_DONE) || (retval == SQLITE_OK)) {
1373 1 quota_mgr->gauge_ -= size;
1374 } else {
1375 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1376 "failed to delete %s (%d)", hash_str.c_str(), retval);
1377 }
1378
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_reset(quota_mgr->stmt_rm_);
1379 }
1380
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_reset(quota_mgr->stmt_size_);
1381 }
1382 } else {
1383 LogCvmfs(kLogQuota, kLogDebug, "this chunk was not pinned");
1384 }
1385 2 }
1386
1387 // Immediate commands trigger flushing of the buffer
1388
2/2
✓ Branch 0 taken 150074 times.
✓ Branch 1 taken 21 times.
150095 bool immediate_command = (command_type == kCleanup) ||
1389
4/4
✓ Branch 0 taken 150066 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 150063 times.
✓ Branch 3 taken 3 times.
150074 (command_type == kList) || (command_type == kListPinned) ||
1390
4/4
✓ Branch 0 taken 150060 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 150057 times.
✓ Branch 3 taken 3 times.
150063 (command_type == kListCatalogs) || (command_type == kListVolatile) ||
1391
3/4
✓ Branch 0 taken 150039 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 150039 times.
✗ Branch 3 not taken.
150057 (command_type == kRemove) || (command_type == kStatus) ||
1392
3/4
✓ Branch 0 taken 150095 times.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 150039 times.
300199 (command_type == kLimits) || (command_type == kPid);
1393
2/2
✓ Branch 0 taken 150039 times.
✓ Branch 1 taken 65 times.
150104 if (!immediate_command) num_commands++;
1394
1395
4/4
✓ Branch 0 taken 145417 times.
✓ Branch 1 taken 4687 times.
✓ Branch 2 taken 65 times.
✓ Branch 3 taken 145352 times.
150104 if ((num_commands == kCommandBufferSize) || immediate_command)
1396 {
1397
1/2
✓ Branch 1 taken 4752 times.
✗ Branch 2 not taken.
4752 quota_mgr->ProcessCommandBunch(num_commands, command_buffer,
1398 description_buffer);
1399
2/2
✓ Branch 0 taken 4687 times.
✓ Branch 1 taken 65 times.
4752 if (!immediate_command) num_commands = 0;
1400 }
1401
1402
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 150039 times.
150104 if (immediate_command) {
1403 // Process cleanup, listings
1404 int return_pipe =
1405
1/2
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
65 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1406
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (return_pipe < 0) {
1407 num_commands = 0;
1408 continue;
1409 }
1410
1411 int retval;
1412 65 sqlite3_stmt *this_stmt_list = NULL;
1413
7/10
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 18 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
65 switch (command_type) {
1414 3 case kRemove: {
1415
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 const shash::Any hash = command_buffer[num_commands].RetrieveHash();
1416
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 const string hash_str = hash.ToString();
1417
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 LogCvmfs(kLogQuota, kLogDebug, "manually removing %s",
1418 hash_str.c_str());
1419 3 bool success = false;
1420
1421
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 sqlite3_bind_text(quota_mgr->stmt_size_, 1, &hash_str[0],
1422 3 hash_str.length(), SQLITE_STATIC);
1423 int retval;
1424
3/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 times.
3 if ((retval = sqlite3_step(quota_mgr->stmt_size_)) == SQLITE_ROW) {
1425
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 uint64_t size = sqlite3_column_int64(quota_mgr->stmt_size_, 0);
1426
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 uint64_t is_pinned = sqlite3_column_int64(quota_mgr->stmt_size_, 1);
1427
1428
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 sqlite3_bind_text(quota_mgr->stmt_rm_, 1, &(hash_str[0]),
1429 2 hash_str.length(), SQLITE_STATIC);
1430
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 retval = sqlite3_step(quota_mgr->stmt_rm_);
1431
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if ((retval == SQLITE_DONE) || (retval == SQLITE_OK)) {
1432 2 success = true;
1433 2 quota_mgr->gauge_ -= size;
1434
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (is_pinned) {
1435
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 quota_mgr->pinned_chunks_.erase(hash);
1436 1 quota_mgr->pinned_ -= size;
1437 }
1438 } else {
1439 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1440 "failed to delete %s (%d)", hash_str.c_str(), retval);
1441 }
1442
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 sqlite3_reset(quota_mgr->stmt_rm_);
1443 } else {
1444 // File does not exist
1445 1 success = true;
1446 }
1447
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 sqlite3_reset(quota_mgr->stmt_size_);
1448
1449
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 WritePipe(return_pipe, &success, sizeof(success));
1450 3 break; }
1451 9 case kCleanup:
1452
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 retval = quota_mgr->DoCleanup(size);
1453
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 WritePipe(return_pipe, &retval, sizeof(retval));
1454 9 break;
1455 21 case kList:
1456
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 if (!this_stmt_list) this_stmt_list = quota_mgr->stmt_list_;
1457 case kListPinned:
1458
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 21 times.
29 if (!this_stmt_list) this_stmt_list = quota_mgr->stmt_list_pinned_;
1459 case kListCatalogs:
1460
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 29 times.
32 if (!this_stmt_list) this_stmt_list = quota_mgr->stmt_list_catalogs_;
1461 case kListVolatile:
1462
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 32 times.
35 if (!this_stmt_list) this_stmt_list = quota_mgr->stmt_list_volatile_;
1463
1464 // Pipe back the list, one by one
1465 int length;
1466
3/4
✓ Branch 1 taken 100074 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 100039 times.
✓ Branch 4 taken 35 times.
100074 while (sqlite3_step(this_stmt_list) == SQLITE_ROW) {
1467
1/2
✓ Branch 2 taken 100039 times.
✗ Branch 3 not taken.
100039 string path = "(NULL)";
1468
2/4
✓ Branch 1 taken 100039 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 100039 times.
✗ Branch 4 not taken.
100039 if (sqlite3_column_type(this_stmt_list, 0) != SQLITE_NULL) {
1469 200078 path = string(
1470 reinterpret_cast<const char *>(
1471
2/4
✓ Branch 1 taken 100039 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 100039 times.
✗ Branch 5 not taken.
100039 sqlite3_column_text(this_stmt_list, 0)));
1472 }
1473 100039 length = path.length();
1474
1/2
✓ Branch 1 taken 100039 times.
✗ Branch 2 not taken.
100039 WritePipe(return_pipe, &length, sizeof(length));
1475
1/2
✓ Branch 0 taken 100039 times.
✗ Branch 1 not taken.
100039 if (length > 0)
1476
2/4
✓ Branch 1 taken 100039 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 100039 times.
✗ Branch 5 not taken.
100039 WritePipe(return_pipe, &path[0], length);
1477 100039 }
1478 35 length = -1;
1479
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 WritePipe(return_pipe, &length, sizeof(length));
1480
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 sqlite3_reset(this_stmt_list);
1481 35 break;
1482 18 case kStatus:
1483
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 WritePipe(return_pipe, &quota_mgr->gauge_, sizeof(quota_mgr->gauge_));
1484
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 WritePipe(return_pipe, &quota_mgr->pinned_,
1485 sizeof(quota_mgr->pinned_));
1486 18 break;
1487 case kLimits:
1488 WritePipe(return_pipe, &quota_mgr->limit_, sizeof(quota_mgr->limit_));
1489 WritePipe(return_pipe, &quota_mgr->cleanup_threshold_,
1490 sizeof(quota_mgr->cleanup_threshold_));
1491 break;
1492 case kPid: {
1493 pid_t pid = getpid();
1494 WritePipe(return_pipe, &pid, sizeof(pid));
1495 break;
1496 }
1497 default:
1498 PANIC(NULL); // other types are handled by the bunch processor
1499 }
1500
1/2
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
65 quota_mgr->UnbindReturnPipe(return_pipe);
1501 65 num_commands = 0;
1502 }
1503 }
1504
1505
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 LogCvmfs(kLogQuota, kLogDebug, "stopping cache manager (%d)", errno);
1506
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 close(quota_mgr->pipe_lru_[0]);
1507
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 quota_mgr->ProcessCommandBunch(num_commands, command_buffer,
1508 description_buffer);
1509
1510 // Unpin
1511 31 command_buffer[0].command_type = kTouch;
1512 31 for (map<shash::Any, uint64_t>::const_iterator i =
1513 31 quota_mgr->pinned_chunks_.begin(),
1514
2/2
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 31 times.
41 iEnd = quota_mgr->pinned_chunks_.end(); i != iEnd; ++i)
1515 {
1516
1/2
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 command_buffer[0].StoreHash(i->first);
1517
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 quota_mgr->ProcessCommandBunch(1, command_buffer, description_buffer);
1518 }
1519
1520 31 return NULL;
1521 }
1522
1523
1524 92 void PosixQuotaManager::MakeReturnPipe(int pipe[2]) {
1525
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 3 times.
92 if (!shared_) {
1526 89 MakePipe(pipe);
1527 89 return;
1528 }
1529
1530 // Create FIFO in cache directory, store path name (number) in pipe write end
1531 3 int i = 0;
1532 int retval;
1533 do {
1534
2/4
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
4 retval = mkfifo((workspace_dir_ + "/pipe" + StringifyInt(i)).c_str(), 0600);
1535 4 pipe[1] = i;
1536 4 i++;
1537
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
4 } while ((retval == -1) && (errno == EEXIST));
1538
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 0);
1539
1540 // Connect reader's end
1541
3/6
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 3 times.
✗ Branch 10 not taken.
3 pipe[0] = open((workspace_dir_ + "/pipe" + StringifyInt(pipe[1])).c_str(),
1542 O_RDONLY | O_NONBLOCK);
1543
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(pipe[0] >= 0);
1544 3 Nonblock2Block(pipe[0]);
1545 }
1546
1547
1548 91 void PosixQuotaManager::ParseDirectories(
1549 const std::string cache_workspace,
1550 std::string *cache_dir,
1551 std::string *workspace_dir)
1552 {
1553
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 vector<string> dir_tokens(SplitString(cache_workspace, ':'));
1554
2/3
✓ Branch 1 taken 89 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
91 switch (dir_tokens.size()) {
1555 89 case 1:
1556
2/4
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 89 times.
✗ Branch 6 not taken.
89 *cache_dir = *workspace_dir = dir_tokens[0];
1557 89 break;
1558 2 case 2:
1559
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 *cache_dir = dir_tokens[0];
1560
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 *workspace_dir = dir_tokens[1];
1561 2 break;
1562 default:
1563 PANIC(NULL);
1564 }
1565 91 }
1566
1567
1568 /**
1569 * Immediately inserts a new pinned catalog. Does cache cleanup if necessary.
1570 *
1571 * \return True on success, false otherwise
1572 */
1573 43 bool PosixQuotaManager::Pin(
1574 const shash::Any &hash,
1575 const uint64_t size,
1576 const string &description,
1577 const bool is_catalog)
1578 {
1579
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 41 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
43 assert((size > 0) || !is_catalog);
1580
1581
1/2
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
43 const string hash_str = hash.ToString();
1582
1/2
✓ Branch 3 taken 43 times.
✗ Branch 4 not taken.
43 LogCvmfs(kLogQuota, kLogDebug, "pin into lru %s, path %s",
1583 hash_str.c_str(), description.c_str());
1584
1585 // Has to run when not yet spawned (cvmfs initialization)
1586
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 14 times.
43 if (!spawned_) {
1587 // Code duplication here
1588
3/4
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 17 times.
✓ Branch 6 taken 12 times.
29 if (pinned_chunks_.find(hash) == pinned_chunks_.end()) {
1589
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 15 times.
17 if (pinned_ + size > cleanup_threshold_) {
1590
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 LogCvmfs(kLogQuota, kLogDebug, "failed to insert %s (pinned), no space",
1591 hash_str.c_str());
1592 2 return false;
1593 } else {
1594
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 pinned_chunks_[hash] = size;
1595 15 pinned_ += size;
1596
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 CheckHighPinWatermark();
1597 }
1598 }
1599
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 bool exists = Contains(hash_str);
1600
4/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 14 times.
27 if (!exists && (gauge_ + size > limit_)) {
1601
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogQuota, kLogDebug, "over limit, gauge %lu, file size %lu",
1602 gauge_, size);
1603
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 int retval = DoCleanup(cleanup_threshold_);
1604
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 assert(retval != 0);
1605 }
1606
1/2
✓ Branch 3 taken 27 times.
✗ Branch 4 not taken.
27 sqlite3_bind_text(stmt_new_, 1, &hash_str[0], hash_str.length(),
1607 SQLITE_STATIC);
1608
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 sqlite3_bind_int64(stmt_new_, 2, size);
1609
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 sqlite3_bind_int64(stmt_new_, 3, seq_++);
1610
1/2
✓ Branch 3 taken 27 times.
✗ Branch 4 not taken.
27 sqlite3_bind_text(stmt_new_, 4, &description[0], description.length(),
1611 SQLITE_STATIC);
1612
3/4
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 27 times.
✗ Branch 4 not taken.
27 sqlite3_bind_int64(stmt_new_, 5, is_catalog ? kFileCatalog : kFileRegular);
1613
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 sqlite3_bind_int64(stmt_new_, 6, 1);
1614
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 int retval = sqlite3_step(stmt_new_);
1615
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
27 assert((retval == SQLITE_DONE) || (retval == SQLITE_OK));
1616
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 sqlite3_reset(stmt_new_);
1617
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 12 times.
27 if (!exists) gauge_ += size;
1618 27 return true;
1619 }
1620
1621 int pipe_reserve[2];
1622
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 MakeReturnPipe(pipe_reserve);
1623
1624 14 LruCommand cmd;
1625 14 cmd.command_type = kReserve;
1626 14 cmd.SetSize(size);
1627
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 cmd.StoreHash(hash);
1628 14 cmd.return_pipe = pipe_reserve[1];
1629
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
1630 bool result;
1631
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 ManagedReadHalfPipe(pipe_reserve[0], &result, sizeof(result));
1632
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 CloseReturnPipe(pipe_reserve);
1633
1634
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (!result) return false;
1635
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
13 DoInsert(hash, size, description, is_catalog ? kPin : kPinRegular);
1636
1637 13 return true;
1638 43 }
1639
1640
1641 89 PosixQuotaManager::PosixQuotaManager(
1642 const uint64_t limit,
1643 const uint64_t cleanup_threshold,
1644 89 const string &cache_workspace)
1645 89 : shared_(false)
1646 89 , spawned_(false)
1647 89 , limit_(limit)
1648 89 , cleanup_threshold_(cleanup_threshold)
1649 89 , gauge_(0)
1650 89 , pinned_(0)
1651 89 , seq_(0)
1652 89 , cache_dir_() // initialized in body
1653 89 , workspace_dir_() // initialized in body
1654 89 , fd_lock_cachedb_(-1)
1655 89 , async_delete_(true)
1656 89 , cachemgr_pid_(0)
1657 89 , database_(NULL)
1658 89 , stmt_touch_(NULL)
1659 89 , stmt_unpin_(NULL)
1660 89 , stmt_block_(NULL)
1661 89 , stmt_unblock_(NULL)
1662 89 , stmt_new_(NULL)
1663 89 , stmt_lru_(NULL)
1664 89 , stmt_size_(NULL)
1665 89 , stmt_rm_(NULL)
1666 89 , stmt_rm_batch_(NULL)
1667 89 , stmt_list_(NULL)
1668 89 , stmt_list_pinned_(NULL)
1669 89 , stmt_list_catalogs_(NULL)
1670 89 , stmt_list_volatile_(NULL)
1671 178 , initialized_(false)
1672 {
1673
2/4
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 89 times.
✗ Branch 5 not taken.
89 ParseDirectories(cache_workspace, &cache_dir_, &workspace_dir_);
1674 89 pipe_lru_[0] = pipe_lru_[1] = -1;
1675
1/2
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
89 cleanup_recorder_.AddRecorder(1, 90); // last 1.5 min with second resolution
1676 // last 1.5 h with minute resolution
1677
1/2
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
89 cleanup_recorder_.AddRecorder(60, 90*60);
1678 // last 18 hours with 20 min resolution
1679
1/2
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
89 cleanup_recorder_.AddRecorder(20*60, 60*60*18);
1680 // last 4 days with hour resolution
1681
1/2
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
89 cleanup_recorder_.AddRecorder(60*60, 60*60*24*4);
1682 89 }
1683
1684
1685 352 PosixQuotaManager::~PosixQuotaManager() {
1686
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 85 times.
176 if (!initialized_) return;
1687
1688
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
170 if (shared_) {
1689 // Most of cleanup is done elsewhen by shared cache manager
1690 close(pipe_lru_[1]);
1691 return;
1692 }
1693
1694
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 54 times.
170 if (spawned_) {
1695 62 char fin = 0;
1696 62 WritePipe(pipe_lru_[1], &fin, 1);
1697 62 close(pipe_lru_[1]);
1698 62 pthread_join(thread_lru_, NULL);
1699 } else {
1700 108 ClosePipe(pipe_lru_);
1701 }
1702
1703 170 CloseDatabase();
1704
10/10
✓ Branch 1 taken 85 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 85 times.
✓ Branch 5 taken 3 times.
✓ Branch 7 taken 85 times.
✓ Branch 8 taken 3 times.
✓ Branch 10 taken 85 times.
✓ Branch 11 taken 3 times.
✓ Branch 13 taken 85 times.
✓ Branch 14 taken 3 times.
376 }
1705
1706
1707 4793 void PosixQuotaManager::ProcessCommandBunch(
1708 const unsigned num,
1709 const LruCommand *commands,
1710 const char *descriptions)
1711 {
1712 4793 int retval = sqlite3_exec(database_, "BEGIN", NULL, NULL, NULL);
1713
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4793 times.
4793 assert(retval == SQLITE_OK);
1714
1715
2/2
✓ Branch 0 taken 150049 times.
✓ Branch 1 taken 4793 times.
154842 for (unsigned i = 0; i < num; ++i) {
1716
1/2
✓ Branch 1 taken 150049 times.
✗ Branch 2 not taken.
150049 const shash::Any hash = commands[i].RetrieveHash();
1717
1/2
✓ Branch 1 taken 150049 times.
✗ Branch 2 not taken.
150049 const string hash_str = hash.ToString();
1718 150049 const unsigned size = commands[i].GetSize();
1719
1/2
✓ Branch 1 taken 150049 times.
✗ Branch 2 not taken.
150049 LogCvmfs(kLogQuota, kLogDebug, "processing %s (%d)",
1720 150049 hash_str.c_str(), commands[i].command_type);
1721
1722 bool exists;
1723
3/4
✓ Branch 0 taken 50013 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 100034 times.
✗ Branch 3 not taken.
150049 switch (commands[i].command_type) {
1724 50013 case kTouch:
1725
1/2
✓ Branch 1 taken 50013 times.
✗ Branch 2 not taken.
50013 sqlite3_bind_int64(stmt_touch_, 1, seq_++);
1726
1/2
✓ Branch 3 taken 50013 times.
✗ Branch 4 not taken.
50013 sqlite3_bind_text(stmt_touch_, 2, &hash_str[0], hash_str.length(),
1727 SQLITE_STATIC);
1728
1/2
✓ Branch 1 taken 50013 times.
✗ Branch 2 not taken.
50013 retval = sqlite3_step(stmt_touch_);
1729
1/2
✓ Branch 1 taken 50013 times.
✗ Branch 2 not taken.
50013 LogCvmfs(kLogQuota, kLogDebug, "touching %s (%ld): %d",
1730 50013 hash_str.c_str(), seq_-1, retval);
1731
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 50013 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
50013 if ((retval != SQLITE_DONE) && (retval != SQLITE_OK)) {
1732 PANIC(kLogSyslogErr, "failed to update %s in cachedb, error %d",
1733 hash_str.c_str(), retval);
1734 }
1735
1/2
✓ Branch 1 taken 50013 times.
✗ Branch 2 not taken.
50013 sqlite3_reset(stmt_touch_);
1736 50013 break;
1737 2 case kUnpin:
1738
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 sqlite3_bind_text(stmt_unpin_, 1, &hash_str[0], hash_str.length(),
1739 SQLITE_STATIC);
1740
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 retval = sqlite3_step(stmt_unpin_);
1741
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 LogCvmfs(kLogQuota, kLogDebug, "unpinning %s: %d",
1742 hash_str.c_str(), retval);
1743
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if ((retval != SQLITE_DONE) && (retval != SQLITE_OK)) {
1744 PANIC(kLogSyslogErr, "failed to unpin %s in cachedb, error %d",
1745 hash_str.c_str(), retval);
1746 }
1747
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 sqlite3_reset(stmt_unpin_);
1748 2 break;
1749 100034 case kPin:
1750 case kPinRegular:
1751 case kInsert:
1752 case kInsertVolatile:
1753 // It could already be in, check
1754
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 exists = Contains(hash_str);
1755
1756 // Cleanup, move to trash and unlink
1757
3/4
✓ Branch 0 taken 100029 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 100029 times.
100034 if (!exists && (gauge_ + size > limit_)) {
1758 LogCvmfs(kLogQuota, kLogDebug, "over limit, gauge %lu, file size %u",
1759 gauge_, size);
1760 retval = DoCleanup(cleanup_threshold_);
1761 assert(retval != 0);
1762 }
1763
1764 // Insert or replace
1765
1/2
✓ Branch 3 taken 100034 times.
✗ Branch 4 not taken.
100034 sqlite3_bind_text(stmt_new_, 1, &hash_str[0], hash_str.length(),
1766 SQLITE_STATIC);
1767
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 sqlite3_bind_int64(stmt_new_, 2, size);
1768
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 100030 times.
100034 if (commands[i].command_type == kInsertVolatile) {
1769
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 sqlite3_bind_int64(stmt_new_, 3, (seq_++) | kVolatileFlag);
1770 } else {
1771
1/2
✓ Branch 1 taken 100030 times.
✗ Branch 2 not taken.
100030 sqlite3_bind_int64(stmt_new_, 3, seq_++);
1772 }
1773 100034 sqlite3_bind_text(stmt_new_, 4, &descriptions[i*kMaxDescription],
1774
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 commands[i].desc_length, SQLITE_STATIC);
1775
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 100032 times.
✓ Branch 3 taken 100034 times.
✗ Branch 4 not taken.
100034 sqlite3_bind_int64(stmt_new_, 5, (commands[i].command_type == kPin) ?
1776 kFileCatalog : kFileRegular);
1777
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 sqlite3_bind_int64(stmt_new_, 6,
1778
2/2
✓ Branch 0 taken 100032 times.
✓ Branch 1 taken 2 times.
100034 ((commands[i].command_type == kPin) ||
1779
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 100021 times.
100032 (commands[i].command_type == kPinRegular)) ? 1 : 0);
1780
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 retval = sqlite3_step(stmt_new_);
1781
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 LogCvmfs(kLogQuota, kLogDebug, "insert or replace %s, method %d: %d",
1782 100034 hash_str.c_str(), commands[i].command_type, retval);
1783
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
100034 if ((retval != SQLITE_DONE) && (retval != SQLITE_OK)) {
1784 PANIC(kLogSyslogErr, "failed to insert %s in cachedb, error %d",
1785 hash_str.c_str(), retval);
1786 }
1787
1/2
✓ Branch 1 taken 100034 times.
✗ Branch 2 not taken.
100034 sqlite3_reset(stmt_new_);
1788
1789
2/2
✓ Branch 0 taken 100029 times.
✓ Branch 1 taken 5 times.
100034 if (!exists) gauge_ += size;
1790 100034 break;
1791 default:
1792 // other types should have been taken care of by event loop
1793 PANIC(NULL);
1794 }
1795 150049 }
1796
1797 4793 retval = sqlite3_exec(database_, "COMMIT", NULL, NULL, NULL);
1798
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4793 times.
4793 if (retval != SQLITE_OK) {
1799 PANIC(kLogSyslogErr, "failed to commit to cachedb, error %d", retval);
1800 }
1801 4793 }
1802
1803
1804 87 bool PosixQuotaManager::RebuildDatabase() {
1805 87 bool result = false;
1806 87 string sql;
1807 87 sqlite3_stmt *stmt_select = NULL;
1808 87 sqlite3_stmt *stmt_insert = NULL;
1809 int sqlerr;
1810 87 int seq = 0;
1811 char hex[4];
1812 struct stat info;
1813 platform_dirent64 *d;
1814 87 DIR *dirp = NULL;
1815 87 string path;
1816
1817
1/2
✓ Branch 1 taken 87 times.
✗ Branch 2 not taken.
87 LogCvmfs(kLogQuota, kLogSyslog | kLogDebug, "re-building cache database");
1818
1819 // Empty cache catalog and fscache
1820
1/2
✓ Branch 1 taken 87 times.
✗ Branch 2 not taken.
87 sql = "DELETE FROM cache_catalog; DELETE FROM fscache;";
1821
1/2
✓ Branch 2 taken 87 times.
✗ Branch 3 not taken.
87 sqlerr = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
1822
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 87 times.
87 if (sqlerr != SQLITE_OK) {
1823 LogCvmfs(kLogQuota, kLogDebug, "could not clear cache database");
1824 goto build_return;
1825 }
1826
1827 87 gauge_ = 0;
1828
1829 // Insert files from cache sub-directories 00 - ff
1830 // TODO(jblomer): fs_traversal
1831
1/2
✓ Branch 1 taken 87 times.
✗ Branch 2 not taken.
87 sqlite3_prepare_v2(database_, "INSERT INTO fscache (sha1, size, actime) "
1832 "VALUES (:sha1, :s, :t);", -1, &stmt_insert, NULL);
1833
1834
2/2
✓ Branch 0 taken 21507 times.
✓ Branch 1 taken 84 times.
21591 for (int i = 0; i <= 0xff; i++) {
1835 21507 snprintf(hex, sizeof(hex), "%02x", i);
1836
3/6
✓ Branch 2 taken 21507 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21507 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 21507 times.
✗ Branch 9 not taken.
21507 path = cache_dir_ + "/" + string(hex);
1837
3/4
✓ Branch 2 taken 21507 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 21504 times.
21507 if ((dirp = opendir(path.c_str())) == NULL) {
1838
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1839 "failed to open directory %s (tmpwatch interfering?)",
1840 path.c_str());
1841 3 goto build_return;
1842 }
1843
3/4
✓ Branch 1 taken 64514 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 43010 times.
✓ Branch 4 taken 21504 times.
64514 while ((d = platform_readdir(dirp)) != NULL) {
1844
3/6
✓ Branch 2 taken 43010 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 43010 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 43010 times.
✗ Branch 9 not taken.
86020 string file_path = path + "/" + string(d->d_name);
1845
1/2
✓ Branch 2 taken 43010 times.
✗ Branch 3 not taken.
43010 if (stat(file_path.c_str(), &info) == 0) {
1846
2/2
✓ Branch 0 taken 43008 times.
✓ Branch 1 taken 2 times.
43010 if (!S_ISREG(info.st_mode))
1847 43009 continue;
1848
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (info.st_size == 0) {
1849
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogCvmfs(kLogQuota, kLogSyslog | kLogDebug,
1850 "removing empty file %s during automatic cache db rebuild",
1851 file_path.c_str());
1852 1 unlink(file_path.c_str());
1853 1 continue;
1854 }
1855
1856
3/6
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
2 string hash = string(hex) + string(d->d_name);
1857
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 sqlite3_bind_text(stmt_insert, 1, hash.data(), hash.length(),
1858 SQLITE_STATIC);
1859
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_bind_int64(stmt_insert, 2, info.st_size);
1860
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_bind_int64(stmt_insert, 3, info.st_atime);
1861
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if (sqlite3_step(stmt_insert) != SQLITE_DONE) {
1862 LogCvmfs(kLogQuota, kLogDebug, "could not insert into temp table");
1863 goto build_return;
1864 }
1865
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_reset(stmt_insert);
1866
1867 1 gauge_ += info.st_size;
1868
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 } else {
1869 LogCvmfs(kLogQuota, kLogDebug, "could not stat %s", file_path.c_str());
1870 }
1871
2/3
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 43009 times.
✗ Branch 3 not taken.
43010 }
1872
1/2
✓ Branch 1 taken 21504 times.
✗ Branch 2 not taken.
21504 closedir(dirp);
1873 21504 dirp = NULL;
1874 }
1875
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 sqlite3_finalize(stmt_insert);
1876 84 stmt_insert = NULL;
1877
1878 // Transfer from temp table in cache catalog
1879
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 sqlite3_prepare_v2(database_,
1880 "SELECT sha1, size FROM fscache ORDER BY actime;",
1881 -1, &stmt_select, NULL);
1882
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 sqlite3_prepare_v2(database_,
1883 "INSERT INTO cache_catalog (sha1, size, acseq, path, type, pinned) "
1884 "VALUES (:sha1, :s, :seq, 'unknown (automatic rebuild)', :t, 0);",
1885 -1, &stmt_insert, NULL);
1886
3/4
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 84 times.
85 while (sqlite3_step(stmt_select) == SQLITE_ROW) {
1887 const string hash = string(
1888
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 reinterpret_cast<const char *>(sqlite3_column_text(stmt_select, 0)));
1889
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 sqlite3_bind_text(stmt_insert, 1, &hash[0], hash.length(), SQLITE_STATIC);
1890
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 sqlite3_bind_int64(stmt_insert, 2, sqlite3_column_int64(stmt_select, 1));
1891
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_bind_int64(stmt_insert, 3, seq++);
1892 // Might also be a catalog (information is lost)
1893
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_bind_int64(stmt_insert, 4, kFileRegular);
1894
1895
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 int retval = sqlite3_step(stmt_insert);
1896
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (retval != SQLITE_DONE) {
1897 // If the file system hosting the cache is full, we'll likely notice here
1898 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1899 "could not insert into cache catalog (%d - %s)",
1900 retval, sqlite3_errstr(retval));
1901 goto build_return;
1902 }
1903
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 sqlite3_reset(stmt_insert);
1904
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 }
1905
1906 // Delete temporary table
1907
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 sql = "DELETE FROM fscache;";
1908
1/2
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
84 sqlerr = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
1909
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 84 times.
84 if (sqlerr != SQLITE_OK) {
1910 LogCvmfs(kLogQuota, kLogDebug, "could not clear temporary table (%d)",
1911 sqlerr);
1912 goto build_return;
1913 }
1914
1915 84 seq_ = seq;
1916 84 result = true;
1917
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 LogCvmfs(kLogQuota, kLogDebug,
1918 "rebuilding finished, sequence %" PRIu64 ", gauge %" PRIu64,
1919 seq_, gauge_);
1920
1921 87 build_return:
1922
2/4
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 87 times.
✗ Branch 4 not taken.
87 if (stmt_insert) sqlite3_finalize(stmt_insert);
1923
3/4
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 84 times.
✗ Branch 4 not taken.
87 if (stmt_select) sqlite3_finalize(stmt_select);
1924
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 87 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
87 if (dirp) closedir(dirp);
1925 87 return result;
1926 87 }
1927
1928
1929 /**
1930 * Register a channel that allows the cache manager to trigger action to its
1931 * clients. Currently used for releasing pinned catalogs.
1932 */
1933 4 void PosixQuotaManager::RegisterBackChannel(
1934 int back_channel[2],
1935 const string &channel_id)
1936 {
1937
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (protocol_revision_ >= 1) {
1938
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 shash::Md5 hash = shash::Md5(shash::AsciiPtr(channel_id));
1939
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 MakeReturnPipe(back_channel);
1940
1941 4 LruCommand cmd;
1942 4 cmd.command_type = kRegisterBackChannel;
1943 4 cmd.return_pipe = back_channel[1];
1944 // Not StoreHash(). This is an MD5 hash.
1945 4 memcpy(cmd.digest, hash.digest, hash.GetDigestSize());
1946
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
1947
1948 char success;
1949
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ManagedReadHalfPipe(back_channel[0], &success, sizeof(success));
1950 // At this point, the named FIFO is unlinked, so don't use CloseReturnPipe
1951
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (success != 'S') {
1952 PANIC(kLogDebug | kLogSyslogErr,
1953 "failed to register quota back channel (%c)", success);
1954 }
1955 } else {
1956 // Dummy pipe to return valid file descriptors
1957 MakePipe(back_channel);
1958 }
1959 4 }
1960
1961
1962 /**
1963 * Removes a chunk from cache, if it exists.
1964 */
1965 3 void PosixQuotaManager::Remove(const shash::Any &hash) {
1966
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 string hash_str = hash.ToString();
1967
1968 int pipe_remove[2];
1969
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 MakeReturnPipe(pipe_remove);
1970
1971 3 LruCommand cmd;
1972 3 cmd.command_type = kRemove;
1973 3 cmd.return_pipe = pipe_remove[1];
1974
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 cmd.StoreHash(hash);
1975
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
1976
1977 bool success;
1978
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 ManagedReadHalfPipe(pipe_remove[0], &success, sizeof(success));
1979
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 CloseReturnPipe(pipe_remove);
1980
1981
3/6
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
3 unlink((cache_dir_ + "/" + hash.MakePathWithoutSuffix()).c_str());
1982 3 }
1983
1984
1985 33 void PosixQuotaManager::Spawn() {
1986
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 31 times.
33 if (spawned_)
1987 2 return;
1988
1989 31 if (pthread_create(&thread_lru_, NULL, MainCommandServer,
1990
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 static_cast<void *>(this)) != 0)
1991 {
1992 PANIC(kLogDebug, "could not create lru thread");
1993 }
1994
1995 31 spawned_ = true;
1996 }
1997
1998
1999 /**
2000 * Updates the sequence number of the file specified by the hash.
2001 */
2002 50017 void PosixQuotaManager::Touch(const shash::Any &hash) {
2003 50017 LruCommand cmd;
2004 50017 cmd.command_type = kTouch;
2005
1/2
✓ Branch 1 taken 50017 times.
✗ Branch 2 not taken.
50017 cmd.StoreHash(hash);
2006
1/2
✓ Branch 1 taken 50017 times.
✗ Branch 2 not taken.
50017 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2007 50017 }
2008
2009
2010 86 void PosixQuotaManager::UnbindReturnPipe(int pipe_wronly) {
2011
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 85 times.
86 if (shared_)
2012 1 close(pipe_wronly);
2013 86 }
2014
2015
2016 7 void PosixQuotaManager::UnlinkReturnPipe(int pipe_wronly) {
2017
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (shared_)
2018
2/4
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
3 unlink((workspace_dir_ + "/pipe" + StringifyInt(pipe_wronly)).c_str());
2019 7 }
2020
2021
2022 19 void PosixQuotaManager::Unpin(const shash::Any &hash) {
2023
2/4
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 19 times.
✗ Branch 6 not taken.
19 LogCvmfs(kLogQuota, kLogDebug, "Unpin %s", hash.ToString().c_str());
2024
2025 19 LruCommand cmd;
2026 19 cmd.command_type = kUnpin;
2027
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 cmd.StoreHash(hash);
2028
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2029 19 }
2030
2031
2032 2 void PosixQuotaManager::UnregisterBackChannel(
2033 int back_channel[2],
2034 const string &channel_id)
2035 {
2036
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (protocol_revision_ >= 1) {
2037
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 shash::Md5 hash = shash::Md5(shash::AsciiPtr(channel_id));
2038
2039 2 LruCommand cmd;
2040 2 cmd.command_type = kUnregisterBackChannel;
2041 // Not StoreHash(). This is an MD5 hash.
2042 2 memcpy(cmd.digest, hash.digest, hash.GetDigestSize());
2043
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2044
2045 // Writer's end will be closed by cache manager, FIFO is already unlinked
2046
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 close(back_channel[0]);
2047 } else {
2048 ClosePipe(back_channel);
2049 }
2050 2 }
2051
2052 100127 void PosixQuotaManager::ManagedReadHalfPipe(int fd, void *buf, size_t nbyte) {
2053
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100127 times.
100127 const unsigned timeout_ms = cachemgr_pid_ ? 1000 : 0;
2054 100127 bool result = false;
2055 do {
2056 100127 result = ReadHalfPipe(fd, buf, nbyte, timeout_ms);
2057 // try only as long as the cachemgr is still alive
2058
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 100127 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 100127 times.
100127 } while (!result && getpgid(cachemgr_pid_) >= 0);
2059
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100127 times.
100127 if (!result) {
2060 PANIC(kLogStderr, "Error: quota manager could not read from cachemanager pipe");
2061 }
2062
2063 100127 }
2064