GCC Code Coverage Report


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