GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/quota_posix.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 918 1224 75.0%
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 1380 int PosixQuotaManager::BindReturnPipe(int pipe_wronly) {
65
2/2
✓ Branch 0 taken 1350 times.
✓ Branch 1 taken 30 times.
1380 if (!shared_)
66 1350 return pipe_wronly;
67
68 // Connect writer's end
69 const int result =
70
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 open((workspace_dir_ + "/pipe" + StringifyInt(pipe_wronly)).c_str(),
71 O_WRONLY | O_NONBLOCK);
72
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 if (result >= 0) {
73 15 Nonblock2Block(result);
74 } else {
75 15 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
76 15 "failed to bind return pipe (%d)", errno);
77 }
78 30 return result;
79 }
80
81
82 827 void PosixQuotaManager::CheckHighPinWatermark() {
83 827 const uint64_t watermark = kHighPinWatermark * cleanup_threshold_ / 100;
84
3/4
✓ Branch 0 taken 827 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✓ Branch 3 taken 782 times.
827 if ((cleanup_threshold_ > 0) && (pinned_ > watermark)) {
85 45 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
86 "high watermark of pinned files (%" PRIu64 "M > %" PRIu64 "M)",
87 45 pinned_ / (1024 * 1024), watermark / (1024 * 1024));
88
2/4
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
45 BroadcastBackchannels("R"); // clients: please release pinned catalogs
89 }
90 827 }
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 135 bool PosixQuotaManager::Cleanup(const uint64_t leave_size) {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 135 times.
135 if (!spawned_)
127 return DoCleanup(leave_size);
128
129 bool result;
130 int pipe_cleanup[2];
131
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 MakeReturnPipe(pipe_cleanup);
132
133 135 LruCommand cmd;
134 135 cmd.command_type = kCleanup;
135 135 cmd.size = leave_size;
136 135 cmd.return_pipe = pipe_cleanup[1];
137
138
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
139
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 ManagedReadHalfPipe(pipe_cleanup[0], &result, sizeof(result));
140
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 CloseReturnPipe(pipe_cleanup);
141
142 135 return result;
143 }
144
145
146 2150 void PosixQuotaManager::CloseDatabase() {
147
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_list_catalogs_)
148 2150 sqlite3_finalize(stmt_list_catalogs_);
149
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_list_pinned_)
150 2150 sqlite3_finalize(stmt_list_pinned_);
151
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_list_volatile_)
152 2150 sqlite3_finalize(stmt_list_volatile_);
153
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_list_)
154 2150 sqlite3_finalize(stmt_list_);
155
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_lru_)
156 2150 sqlite3_finalize(stmt_lru_);
157
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_rm_)
158 2150 sqlite3_finalize(stmt_rm_);
159
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_rm_batch_)
160 2150 sqlite3_finalize(stmt_rm_batch_);
161
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_size_)
162 2150 sqlite3_finalize(stmt_size_);
163
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_touch_)
164 2150 sqlite3_finalize(stmt_touch_);
165
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_unpin_)
166 2150 sqlite3_finalize(stmt_unpin_);
167
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_block_)
168 2150 sqlite3_finalize(stmt_block_);
169
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_unblock_)
170 2150 sqlite3_finalize(stmt_unblock_);
171
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (stmt_new_)
172 2150 sqlite3_finalize(stmt_new_);
173
1/2
✓ Branch 0 taken 2150 times.
✗ Branch 1 not taken.
2150 if (database_)
174 2150 sqlite3_close(database_);
175 2150 UnlockFile(fd_lock_cachedb_);
176
177 2150 stmt_list_catalogs_ = NULL;
178 2150 stmt_list_pinned_ = NULL;
179 2150 stmt_list_volatile_ = NULL;
180 2150 stmt_list_ = NULL;
181 2150 stmt_rm_ = NULL;
182 2150 stmt_rm_batch_ = NULL;
183 2150 stmt_size_ = NULL;
184 2150 stmt_touch_ = NULL;
185 2150 stmt_unpin_ = NULL;
186 2150 stmt_block_ = NULL;
187 2150 stmt_unblock_ = NULL;
188 2150 stmt_new_ = NULL;
189 2150 database_ = NULL;
190
191 2150 pinned_chunks_.clear();
192 2150 }
193
194
195 1305 void PosixQuotaManager::CloseReturnPipe(int pipe[2]) {
196
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1275 times.
1305 if (shared_) {
197 30 close(pipe[0]);
198 30 UnlinkReturnPipe(pipe[1]);
199 } else {
200 1275 ClosePipe(pipe);
201 }
202 1305 }
203
204
205 1501766 bool PosixQuotaManager::Contains(const string &hash_str) {
206 1501766 bool result = false;
207
208 1501766 sqlite3_bind_text(stmt_size_, 1, &hash_str[0], hash_str.length(),
209 SQLITE_STATIC);
210
2/2
✓ Branch 1 taken 654 times.
✓ Branch 2 taken 1501112 times.
1501766 if (sqlite3_step(stmt_size_) == SQLITE_ROW)
211 654 result = true;
212 1501766 sqlite3_reset(stmt_size_);
213 1501766 LogCvmfs(kLogQuota, kLogDebug, "contains %s returns %d", hash_str.c_str(),
214 result);
215
216 1501766 return result;
217 }
218
219
220 2121 void PosixQuotaManager::CheckFreeSpace() {
221
3/4
✓ Branch 0 taken 2121 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 2106 times.
2121 if ((limit_ == 0) || (gauge_ >= limit_))
222 15 return;
223
224 struct statvfs vfs_info;
225
1/2
✓ Branch 1 taken 2106 times.
✗ Branch 2 not taken.
2106 const int retval = statvfs((cache_dir_ + "/cachedb").c_str(), &vfs_info);
226
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2106 times.
2106 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 2106 const int64_t free_space_byte = vfs_info.f_bavail * vfs_info.f_bsize;
233
1/2
✓ Branch 1 taken 2106 times.
✗ Branch 2 not taken.
2106 LogCvmfs(kLogQuota, kLogDebug, "free space: %" PRId64 " MB",
234 free_space_byte / (1024 * 1024));
235
236 2106 const int64_t required_byte = limit_ - gauge_;
237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2106 times.
2106 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 2166 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 30 times.
✓ Branch 1 taken 2136 times.
2166 if (cleanup_threshold >= limit) {
251 30 LogCvmfs(kLogQuota, kLogDebug,
252 "invalid parameters: limit %" PRIu64 ", "
253 "cleanup_threshold %" PRIu64,
254 limit, cleanup_threshold);
255 30 return NULL;
256 }
257
258 PosixQuotaManager *quota_manager = new PosixQuotaManager(
259
1/2
✓ Branch 2 taken 2136 times.
✗ Branch 3 not taken.
2136 limit, cleanup_threshold, cache_workspace);
260
261 // Initialize cache catalog
262
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 2121 times.
2136 if (!quota_manager->InitDatabase(rebuild_database)) {
263
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 delete quota_manager;
264 15 return NULL;
265 }
266 2121 quota_manager->CheckFreeSpace();
267 2121 MakePipe(quota_manager->pipe_lru_);
268
269 2121 quota_manager->protocol_revision_ = kProtocolRevision;
270 2121 quota_manager->initialized_ = true;
271 2121 return quota_manager;
272 }
273
274
275 /**
276 * Connects to a running shared local quota manager. Creates one if necessary.
277 */
278 30 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 30 string cache_dir;
285 30 string workspace_dir;
286
2/4
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
30 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 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
30 const int fd_lockfile = LockFile(workspace_dir + "/lock_cachemgr");
292
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 if (fd_lockfile < 0) {
293
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "could not open lock file %s (%d)",
294
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
30 (workspace_dir + "/lock_cachemgr").c_str(), errno);
295 15 return NULL;
296 }
297
298 PosixQuotaManager *quota_mgr = new PosixQuotaManager(limit, cleanup_threshold,
299
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 cache_workspace);
300 15 quota_mgr->shared_ = true;
301 15 quota_mgr->spawned_ = true;
302
303 // Try to connect to pipe
304
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const string fifo_path = workspace_dir + "/cachemgr";
305
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "trying to connect to existing pipe");
306
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 quota_mgr->pipe_lru_[1] = open(fifo_path.c_str(), O_WRONLY | O_NONBLOCK);
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 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 15 const int connect_error = errno;
354
355 // Lock file: let existing cache manager finish first
356
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 const int fd_lockfile_fifo = LockFile(workspace_dir + "/lock_cachemgr.fifo");
357
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 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 15 times.
✗ Branch 2 not taken.
15 UnlockFile(fd_lockfile_fifo);
365
366
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 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 15 int retval = mkfifo(fifo_path.c_str(), 0600);
373
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 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 15 times.
✗ Branch 2 not taken.
15 MakePipe(pipe_boot);
385
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 MakePipe(pipe_handshake);
386
387 15 vector<string> command_line;
388
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 command_line.push_back(exe_path);
389
2/4
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 command_line.push_back("__cachemgr__");
390
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 command_line.push_back(cache_workspace);
391
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 command_line.push_back(StringifyInt(pipe_boot[1]));
392
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 command_line.push_back(StringifyInt(pipe_handshake[0]));
393
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 command_line.push_back(StringifyInt(limit));
394
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 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 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 command_line.push_back(StringifyInt(true)); // foreground
398
3/6
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 15 times.
✗ Branch 8 not taken.
15 command_line.push_back(StringifyInt(GetLogSyslogLevel()));
399
3/6
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 15 times.
✗ Branch 8 not taken.
15 command_line.push_back(StringifyInt(GetLogSyslogFacility()));
400
5/14
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 15 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 15 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 15 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
15 command_line.push_back(GetLogDebugFile() + ":" + GetLogMicroSyslog());
401
402 15 set<int> preserve_filedes;
403
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 preserve_filedes.insert(0);
404
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 preserve_filedes.insert(1);
405
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 preserve_filedes.insert(2);
406
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 preserve_filedes.insert(pipe_boot[1]);
407
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 preserve_filedes.insert(pipe_handshake[0]);
408
409
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (foreground) {
410
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 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 15 times.
15 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 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "new cache manager pid: %d", new_cachemgr_pid);
426 15 quota_mgr->SetCacheMgrPid(new_cachemgr_pid);
427
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 const int fd_lockfile_rw = open((workspace_dir + "/lock_cachemgr").c_str(),
428 O_RDWR | O_TRUNC, 0600);
429 15 const unsigned magic_number = PosixQuotaManager::kLockFileMagicNumber;
430
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const bool result_mn = SafeWrite(fd_lockfile_rw, &magic_number,
431 sizeof(magic_number));
432
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const bool result = SafeWrite(fd_lockfile_rw, &new_cachemgr_pid,
433 sizeof(new_cachemgr_pid));
434
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
15 if (!result || !result_mn) {
435 PANIC(kLogSyslogErr, "could not write cache manager pid to lockfile");
436 }
437
438
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 close(fd_lockfile_rw);
439 // Wait for cache manager to be ready
440
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 close(pipe_boot[1]);
441
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 close(pipe_handshake[0]);
442 char buf;
443
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
15 if (read(pipe_boot[0], &buf, 1) != 1) {
444
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 UnlockFile(fd_lockfile);
445
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 close(pipe_boot[0]);
446
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 close(pipe_handshake[1]);
447
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 delete quota_mgr;
448
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
449 "cache manager did not start");
450 15 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 30 }
490
491
492 150 bool PosixQuotaManager::DoCleanup(const uint64_t leave_size) {
493
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 120 times.
150 if (gauge_ <= leave_size)
494 30 return true;
495
496 // TODO(jblomer) transaction
497
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 LogCvmfs(kLogQuota, kLogSyslog | kLogDebug,
498 "clean up cache until at most %lu KB is used", leave_size / 1024);
499
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 LogCvmfs(kLogQuota, kLogDebug, "gauge %" PRIu64, gauge_);
500
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 cleanup_recorder_.Tick();
501
502 bool result;
503 120 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 120 int64_t max_acseq = -1;
509 do {
510
1/2
✓ Branch 1 taken 870 times.
✗ Branch 2 not taken.
870 sqlite3_reset(stmt_lru_);
511
3/4
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 750 times.
✓ Branch 3 taken 870 times.
✗ Branch 4 not taken.
990 sqlite3_bind_int64(stmt_lru_, 1,
512 120 (max_acseq == -1) ? std::numeric_limits<int64_t>::min()
513 : (max_acseq + 1));
514
515 870 std::vector<EvictCandidate> candidates;
516
1/2
✓ Branch 1 taken 870 times.
✗ Branch 2 not taken.
870 candidates.reserve(kEvictBatchSize);
517 870 string hash_str;
518 870 unsigned i = 0;
519
3/4
✓ Branch 1 taken 781020 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 780150 times.
✓ Branch 4 taken 870 times.
781020 while (sqlite3_step(stmt_lru_) == SQLITE_ROW) {
520 hash_str = reinterpret_cast<const char *>(
521
2/4
✓ Branch 1 taken 780150 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 780150 times.
✗ Branch 5 not taken.
780150 sqlite3_column_text(stmt_lru_, 0));
522
1/2
✓ Branch 2 taken 780150 times.
✗ Branch 3 not taken.
780150 LogCvmfs(kLogQuota, kLogDebug, "add %s to candidates for eviction",
523 hash_str.c_str());
524
1/2
✓ Branch 1 taken 780150 times.
✗ Branch 2 not taken.
780150 candidates.push_back(
525
1/2
✓ Branch 1 taken 780150 times.
✗ Branch 2 not taken.
780150 EvictCandidate(shash::MkFromHexPtr(shash::HexPtr(hash_str)),
526 780150 sqlite3_column_int64(stmt_lru_, 1),
527
2/4
✓ Branch 1 taken 780150 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 780150 times.
✗ Branch 5 not taken.
780150 sqlite3_column_int64(stmt_lru_, 2)));
528 780150 i++;
529 }
530
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 855 times.
870 if (candidates.empty()) {
531
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "no more entries to evict");
532 15 break;
533 }
534
535 855 const unsigned N = candidates.size();
536
2/2
✓ Branch 0 taken 750135 times.
✓ Branch 1 taken 750 times.
750885 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 750135 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 15 times.
✓ Branch 7 taken 750120 times.
750135 if (pinned_chunks_.find(candidates[i].hash) != pinned_chunks_.end()) {
541
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 hash_str = candidates[i].hash.ToString();
542
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "skip %s for eviction",
543 hash_str.c_str());
544
2/4
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 sqlite3_bind_text(stmt_block_, 1, &hash_str[0], hash_str.length(),
545 SQLITE_STATIC);
546
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 result = (sqlite3_step(stmt_block_) == SQLITE_DONE);
547
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_reset(stmt_block_);
548
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 assert(result);
549 15 continue;
550 }
551
552
2/4
✓ Branch 1 taken 750120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 750120 times.
✗ Branch 5 not taken.
1500240 trash.push_back(cache_dir_ + "/"
553
2/4
✓ Branch 2 taken 750120 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 750120 times.
✗ Branch 6 not taken.
2250360 + candidates[i].hash.MakePathWithoutSuffix());
554 750120 gauge_ -= candidates[i].size;
555 750120 max_acseq = candidates[i].acseq;
556
1/2
✓ Branch 2 taken 750120 times.
✗ Branch 3 not taken.
750120 LogCvmfs(kLogQuota, kLogDebug, "lru cleanup %s, new gauge %" PRIu64,
557
1/2
✓ Branch 2 taken 750120 times.
✗ Branch 3 not taken.
1500240 candidates[i].hash.ToString().c_str(), gauge_);
558
559
2/2
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 750015 times.
750120 if (gauge_ <= leave_size)
560 105 break;
561 }
562
6/6
✓ Branch 1 taken 855 times.
✓ Branch 2 taken 15 times.
✓ Branch 4 taken 855 times.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 750 times.
✓ Branch 7 taken 105 times.
1740 } while (gauge_ > leave_size);
563
564
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 if (max_acseq != -1) {
565
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 sqlite3_bind_int64(stmt_rm_batch_, 1, max_acseq);
566
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 result = (sqlite3_step(stmt_rm_batch_) == SQLITE_DONE);
567
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 assert(result);
568
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 sqlite3_reset(stmt_rm_batch_);
569
570
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 result = (sqlite3_step(stmt_unblock_) == SQLITE_DONE);
571
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 sqlite3_reset(stmt_unblock_);
572
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 assert(result);
573 }
574
575
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 120 times.
120 if (!EmptyTrash(trash))
576 return false;
577
578
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 105 times.
120 if (gauge_ > leave_size) {
579
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogWarn,
580 "request to clean until %" PRIu64 ", "
581 "but effective gauge is %" PRIu64,
582 leave_size, gauge_);
583 15 return false;
584 }
585 105 return true;
586 120 }
587
588 120 bool PosixQuotaManager::EmptyTrash(const std::vector<std::string> &trash) {
589
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 120 times.
120 if (trash.empty())
590 return true;
591
592
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 30 times.
120 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 90 times.
90 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 90 times.
✗ Branch 1 not taken.
90 if (pid > 0)
614
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 waitpid(pid, &statloc, 0);
615 else
616 return false;
617 }
618 } else { // !async_delete_
619
2/2
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 30 times.
75 for (unsigned i = 0, iEnd = trash.size(); i < iEnd; ++i) {
620 45 LogCvmfs(kLogQuota, kLogDebug, "unlink %s", trash[i].c_str());
621 45 unlink(trash[i].c_str());
622 }
623 }
624 120 return true;
625 }
626
627
628 1500899 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 1500899 times.
✗ Branch 2 not taken.
1500899 const string hash_str = hash.ToString();
633
1/2
✓ Branch 3 taken 1500899 times.
✗ Branch 4 not taken.
1500899 LogCvmfs(kLogQuota, kLogDebug, "insert into lru %s, path %s, method %d",
634 hash_str.c_str(), description.c_str(), command_type);
635 1500899 const unsigned desc_length = (description.length() > kMaxDescription)
636 ? kMaxDescription
637
1/2
✓ Branch 0 taken 1500899 times.
✗ Branch 1 not taken.
1500899 : description.length();
638
639 LruCommand *cmd = reinterpret_cast<LruCommand *>(
640 1500899 alloca(sizeof(LruCommand) + desc_length));
641 1500899 new (cmd) LruCommand;
642 1500899 cmd->command_type = command_type;
643 1500899 cmd->SetSize(size);
644
1/2
✓ Branch 1 taken 1500899 times.
✗ Branch 2 not taken.
1500899 cmd->StoreHash(hash);
645 1500899 cmd->desc_length = desc_length;
646 1500899 memcpy(reinterpret_cast<char *>(cmd) + sizeof(LruCommand), &description[0],
647 desc_length);
648
1/2
✓ Branch 1 taken 1500899 times.
✗ Branch 2 not taken.
1500899 WritePipe(pipe_lru_[1], cmd, sizeof(LruCommand) + desc_length);
649 1500899 }
650
651
652 525 vector<string> PosixQuotaManager::DoList(const CommandType list_command) {
653 525 vector<string> result;
654
655 int pipe_list[2];
656
1/2
✓ Branch 1 taken 525 times.
✗ Branch 2 not taken.
525 MakeReturnPipe(pipe_list);
657 char description_buffer[kMaxDescription];
658
659 525 LruCommand cmd;
660 525 cmd.command_type = list_command;
661 525 cmd.return_pipe = pipe_list[1];
662
1/2
✓ Branch 1 taken 525 times.
✗ Branch 2 not taken.
525 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
663
664 int length;
665 do {
666
1/2
✓ Branch 1 taken 1501110 times.
✗ Branch 2 not taken.
1501110 ManagedReadHalfPipe(pipe_list[0], &length, sizeof(length));
667
2/2
✓ Branch 0 taken 1500585 times.
✓ Branch 1 taken 525 times.
1501110 if (length > 0) {
668
1/2
✓ Branch 1 taken 1500585 times.
✗ Branch 2 not taken.
1500585 ReadPipe(pipe_list[0], description_buffer, length);
669
2/4
✓ Branch 2 taken 1500585 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1500585 times.
✗ Branch 6 not taken.
1500585 result.push_back(string(description_buffer, length));
670 }
671
2/2
✓ Branch 0 taken 1500585 times.
✓ Branch 1 taken 525 times.
1501110 } while (length >= 0);
672
673
1/2
✓ Branch 1 taken 525 times.
✗ Branch 2 not taken.
525 CloseReturnPipe(pipe_list);
674 1050 return result;
675 }
676
677
678 1341 uint64_t PosixQuotaManager::GetCapacity() {
679
1/2
✓ Branch 0 taken 1341 times.
✗ Branch 1 not taken.
1341 if (limit_ != (uint64_t)(-1))
680 1341 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 340 uint64_t PosixQuotaManager::GetMaxFileSize() {
714 340 return limit_ - cleanup_threshold_;
715 }
716
717
718 15 pid_t PosixQuotaManager::GetPid() {
719
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
15 if (!shared_ || !spawned_) {
720 15 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 15 uint32_t PosixQuotaManager::GetProtocolRevision() {
741 int pipe_revision[2];
742
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 MakeReturnPipe(pipe_revision);
743
744 15 LruCommand cmd;
745 15 cmd.command_type = kGetProtocolRevision;
746 15 cmd.return_pipe = pipe_revision[1];
747
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
748
749 uint32_t revision;
750
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 ManagedReadHalfPipe(pipe_revision[0], &revision, sizeof(revision));
751
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 CloseReturnPipe(pipe_revision);
752 15 return revision;
753 }
754
755
756 /**
757 * Queries the shared local hard disk quota manager.
758 */
759 270 void PosixQuotaManager::GetSharedStatus(uint64_t *gauge, uint64_t *pinned) {
760 int pipe_status[2];
761
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 MakeReturnPipe(pipe_status);
762
763 270 LruCommand cmd;
764 270 cmd.command_type = kStatus;
765 270 cmd.return_pipe = pipe_status[1];
766
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
767
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 ManagedReadHalfPipe(pipe_status[0], gauge, sizeof(*gauge));
768
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 ReadPipe(pipe_status[0], pinned, sizeof(*pinned));
769
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 CloseReturnPipe(pipe_status);
770 270 }
771
772 15 bool PosixQuotaManager::SetSharedLimit(uint64_t limit) {
773 int pipe_set_limit[2];
774 bool result;
775
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 MakeReturnPipe(pipe_set_limit);
776
777 15 LruCommand cmd;
778 15 cmd.command_type = kSetLimit;
779 15 cmd.size = limit;
780 15 cmd.return_pipe = pipe_set_limit[1];
781
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
782
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 ReadHalfPipe(pipe_set_limit[0], &result, sizeof(result));
783
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 CloseReturnPipe(pipe_set_limit);
784 15 return result;
785 }
786
787
788 15 bool PosixQuotaManager::SetLimit(uint64_t size) {
789
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 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 15 return SetSharedLimit(size);
798 }
799
800 2892 uint64_t PosixQuotaManager::GetSize() {
801
2/2
✓ Branch 0 taken 2652 times.
✓ Branch 1 taken 240 times.
2892 if (!spawned_)
802 2652 return gauge_;
803 uint64_t gauge, size_pinned;
804
1/2
✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
240 GetSharedStatus(&gauge, &size_pinned);
805 240 return gauge;
806 }
807
808
809 30 uint64_t PosixQuotaManager::GetSizePinned() {
810
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if (!spawned_)
811 return pinned_;
812 uint64_t gauge, size_pinned;
813
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 GetSharedStatus(&gauge, &size_pinned);
814 30 return size_pinned;
815 }
816
817
818 60 uint64_t PosixQuotaManager::GetCleanupRate(uint64_t period_s) {
819
2/4
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
60 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 60 times.
✗ Branch 2 not taken.
60 MakeReturnPipe(pipe_cleanup_rate);
825 60 LruCommand cmd;
826 60 cmd.command_type = kCleanupRate;
827 60 cmd.size = period_s;
828 60 cmd.return_pipe = pipe_cleanup_rate[1];
829
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
830
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 ManagedReadHalfPipe(pipe_cleanup_rate[0], &cleanup_rate,
831 sizeof(cleanup_rate));
832
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 CloseReturnPipe(pipe_cleanup_rate);
833
834 60 return cleanup_rate;
835 }
836
837
838 2211 bool PosixQuotaManager::InitDatabase(const bool rebuild_database) {
839 2211 string sql;
840 sqlite3_stmt *stmt;
841
842
2/4
✓ Branch 1 taken 2211 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2211 times.
✗ Branch 5 not taken.
2211 fd_lock_cachedb_ = LockFile(workspace_dir_ + "/lock_cachedb");
843
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2196 times.
2211 if (fd_lock_cachedb_ < 0) {
844
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "failed to create cachedb lock");
845 15 return false;
846 }
847
848 2196 bool retry = false;
849
1/2
✓ Branch 1 taken 2196 times.
✗ Branch 2 not taken.
2196 const string db_file = cache_dir_ + "/cachedb";
850
2/2
✓ Branch 0 taken 2087 times.
✓ Branch 1 taken 109 times.
2196 if (rebuild_database) {
851
1/2
✓ Branch 2 taken 109 times.
✗ Branch 3 not taken.
109 LogCvmfs(kLogQuota, kLogDebug, "rebuild database, unlinking existing (%s)",
852 db_file.c_str());
853 109 unlink(db_file.c_str());
854
1/2
✓ Branch 1 taken 109 times.
✗ Branch 2 not taken.
109 unlink((db_file + "-journal").c_str());
855 }
856
857 2087 init_recover:
858
1/2
✓ Branch 2 taken 2196 times.
✗ Branch 3 not taken.
2196 int err = sqlite3_open(db_file.c_str(), &database_);
859
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2196 times.
2196 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 2196 times.
✗ Branch 2 not taken.
2196 " CONSTRAINT pk_properties PRIMARY KEY(key));";
876
1/2
✓ Branch 2 taken 2196 times.
✗ Branch 3 not taken.
2196 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
877
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2196 times.
2196 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 2196 times.
✗ Branch 2 not taken.
2196 "ALTER TABLE cache_catalog ADD pinned INTEGER";
896
1/2
✓ Branch 2 taken 2196 times.
✗ Branch 3 not taken.
2196 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
897
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2196 times.
2196 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 2196 times.
✗ Branch 2 not taken.
2196 sql = "UPDATE cache_catalog SET pinned=0;";
909
1/2
✓ Branch 2 taken 2196 times.
✗ Branch 3 not taken.
2196 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
910
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2196 times.
2196 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 2196 times.
✗ Branch 2 not taken.
2196 "VALUES ('schema', '1.0')";
919
1/2
✓ Branch 2 taken 2196 times.
✗ Branch 3 not taken.
2196 err = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
920
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2196 times.
2196 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 2196 times.
✗ Branch 2 not taken.
2196 sql = "SELECT count(*) FROM cache_catalog;";
928
1/2
✓ Branch 2 taken 2196 times.
✗ Branch 3 not taken.
2196 sqlite3_prepare_v2(database_, sql.c_str(), -1, &stmt, NULL);
929
2/4
✓ Branch 1 taken 2196 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2196 times.
✗ Branch 4 not taken.
2196 if (sqlite3_step(stmt) == SQLITE_ROW) {
930
6/8
✓ Branch 1 taken 2196 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
✓ Branch 4 taken 2136 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
✓ Branch 7 taken 2136 times.
✓ Branch 8 taken 60 times.
2196 if ((sqlite3_column_int64(stmt, 0)) == 0 || rebuild_database) {
931
1/2
✓ Branch 1 taken 2136 times.
✗ Branch 2 not taken.
2136 LogCvmfs(kLogCvmfs, kLogDebug,
932 "CernVM-FS: building lru cache database...");
933
3/4
✓ Branch 1 taken 2136 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 45 times.
✓ Branch 4 taken 2091 times.
2136 if (!RebuildDatabase()) {
934
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 LogCvmfs(kLogQuota, kLogDebug,
935 "could not build cache database from file system");
936
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 sqlite3_finalize(stmt);
937 45 goto init_database_fail;
938 }
939 }
940
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 sql = "SELECT sum(size) FROM cache_catalog;";
949
1/2
✓ Branch 2 taken 2151 times.
✗ Branch 3 not taken.
2151 sqlite3_prepare_v2(database_, sql.c_str(), -1, &stmt, NULL);
950
2/4
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2151 times.
✗ Branch 4 not taken.
2151 if (sqlite3_step(stmt) == SQLITE_ROW) {
951
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 sqlite3_finalize(stmt);
958
959 // Highest seq-no?
960
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 sql = "SELECT coalesce(max(acseq & (~(1<<63))), 0) FROM cache_catalog;";
961
1/2
✓ Branch 2 taken 2151 times.
✗ Branch 3 not taken.
2151 sqlite3_prepare_v2(database_, sql.c_str(), -1, &stmt, NULL);
962
2/4
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2151 times.
✗ Branch 4 not taken.
2151 if (sqlite3_step(stmt) == SQLITE_ROW) {
963
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 sqlite3_finalize(stmt);
970
971 // Prepare touch, new, remove statements
972
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 sqlite3_prepare_v2(database_, "DELETE FROM cache_catalog WHERE sha1=:sha1;",
997 -1, &stmt_rm_, NULL);
998
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 3 not taken.
2151 sqlite3_prepare_v2(database_,
1002
1/2
✓ Branch 2 taken 2151 times.
✗ Branch 3 not taken.
4302 (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 2151 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2151 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2151 times.
✗ Branch 8 not taken.
8604 + StringifyInt(kEvictBatchSize) + ";")
1007 .c_str(),
1008 -1, &stmt_lru_, NULL);
1009
1/2
✓ Branch 2 taken 2151 times.
✗ Branch 3 not taken.
2151 sqlite3_prepare_v2(database_,
1010 ("SELECT path FROM cache_catalog WHERE type="
1011
3/6
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2151 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2151 times.
✗ Branch 8 not taken.
4302 + StringifyInt(kFileRegular) + ";")
1012 .c_str(),
1013 -1, &stmt_list_, NULL);
1014
1/2
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 2 not taken.
2151 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 2151 times.
✗ Branch 3 not taken.
2151 sqlite3_prepare_v2(database_,
1021 ("SELECT path FROM cache_catalog WHERE type="
1022
3/6
✓ Branch 1 taken 2151 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2151 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2151 times.
✗ Branch 8 not taken.
4302 + StringifyInt(kFileCatalog) + ";")
1023 .c_str(),
1024 -1, &stmt_list_catalogs_, NULL);
1025 2151 return true;
1026
1027 45 init_database_fail:
1028
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 sqlite3_close(database_);
1029 45 database_ = NULL;
1030
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 UnlockFile(fd_lock_cachedb_);
1031 45 return false;
1032 2211 }
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 1500644 void PosixQuotaManager::Insert(const shash::Any &any_hash,
1040 const uint64_t size,
1041 const string &description) {
1042 1500644 DoInsert(any_hash, size, description, kInsert);
1043 1500644 }
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 60 void PosixQuotaManager::InsertVolatile(const shash::Any &any_hash,
1052 const uint64_t size,
1053 const string &description) {
1054 60 DoInsert(any_hash, size, description, kInsertVolatile);
1055 60 }
1056
1057
1058 /**
1059 * Lists all path names from the cache db.
1060 */
1061 315 vector<string> PosixQuotaManager::List() { return DoList(kList); }
1062
1063
1064 /**
1065 * Lists all pinned files from the cache db.
1066 */
1067 120 vector<string> PosixQuotaManager::ListPinned() { return DoList(kListPinned); }
1068
1069
1070 /**
1071 * Lists all sqlite catalog files from the cache db.
1072 */
1073 45 vector<string> PosixQuotaManager::ListCatalogs() {
1074 45 return DoList(kListCatalogs);
1075 }
1076
1077
1078 /**
1079 * Lists only files flagged as volatile (priority removal)
1080 */
1081 45 vector<string> PosixQuotaManager::ListVolatile() {
1082 45 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 465 void *PosixQuotaManager::MainCommandServer(void *data) {
1225 465 PosixQuotaManager *quota_mgr = static_cast<PosixQuotaManager *>(data);
1226
1227
1/2
✓ Branch 1 taken 465 times.
✗ Branch 2 not taken.
465 LogCvmfs(kLogQuota, kLogDebug, "starting quota manager");
1228
1/2
✓ Branch 1 taken 465 times.
✗ Branch 2 not taken.
465 sqlite3_soft_heap_limit(quota_mgr->kSqliteMemPerThread);
1229
1230
2/2
✓ Branch 1 taken 14880 times.
✓ Branch 2 taken 465 times.
15345 LruCommand command_buffer[kCommandBufferSize];
1231 char description_buffer[kCommandBufferSize * kMaxDescription];
1232 465 unsigned num_commands = 0;
1233
1234
1/2
✓ Branch 1 taken 2252415 times.
✗ Branch 2 not taken.
2252415 while (read(quota_mgr->pipe_lru_[0], &command_buffer[num_commands],
1235 sizeof(command_buffer[0]))
1236
2/2
✓ Branch 0 taken 2251950 times.
✓ Branch 1 taken 465 times.
2252415 == sizeof(command_buffer[0])) {
1237 2251950 const CommandType command_type = command_buffer[num_commands].command_type;
1238
1/2
✓ Branch 1 taken 2251950 times.
✗ Branch 2 not taken.
2251950 LogCvmfs(kLogQuota, kLogDebug, "received command %d", command_type);
1239 2251950 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 751695 times.
✓ Branch 1 taken 1500255 times.
✓ Branch 2 taken 751635 times.
✓ Branch 3 taken 60 times.
2251950 if ((command_type == kInsert) || (command_type == kInsertVolatile)
1243
4/4
✓ Branch 0 taken 751605 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 165 times.
✓ Branch 3 taken 751440 times.
751635 || (command_type == kPin) || (command_type == kPinRegular)) {
1244 1500510 const int desc_length = command_buffer[num_commands].desc_length;
1245 1500510 ReadPipe(quota_mgr->pipe_lru_[0],
1246
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 &description_buffer[kMaxDescription * num_commands],
1247 desc_length);
1248 }
1249
1250 // The protocol revision is returned immediately
1251
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2251935 times.
2251950 if (command_type == kGetProtocolRevision) {
1252 const int return_pipe =
1253
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1254
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (return_pipe < 0)
1255 continue;
1256
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 WritePipe(return_pipe, &quota_mgr->kProtocolRevision,
1257 sizeof(quota_mgr->kProtocolRevision));
1258
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 quota_mgr->UnbindReturnPipe(return_pipe);
1259 15 continue;
1260 15 }
1261
1262 // The cleanup rate is returned immediately
1263
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 2251875 times.
2251935 if (command_type == kCleanupRate) {
1264 const int return_pipe =
1265
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
60 if (return_pipe < 0)
1267 continue;
1268 60 const uint64_t period_s =
1269 size; // use the size field to transmit the period
1270
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 uint64_t rate = quota_mgr->cleanup_recorder_.GetNoTicks(period_s);
1271
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 WritePipe(return_pipe, &rate, sizeof(rate));
1272
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 quota_mgr->UnbindReturnPipe(return_pipe);
1273 60 continue;
1274 60 }
1275
1276
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2251860 times.
2251875 if (command_type == kSetLimit) {
1277
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const int return_pipe = quota_mgr->BindReturnPipe(
1278 command_buffer[num_commands].return_pipe);
1279
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (return_pipe < 0)
1280 continue;
1281 15 quota_mgr->limit_ = size; // use the size field to transmit the size
1282 15 quota_mgr->cleanup_threshold_ = size / 2;
1283
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug | kLogSyslog,
1284 "Quota limit set to %lu / threshold %lu", quota_mgr->limit_,
1285 quota_mgr->cleanup_threshold_);
1286 15 bool ret = true;
1287
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 WritePipe(return_pipe, &ret, sizeof(ret));
1288
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 quota_mgr->UnbindReturnPipe(return_pipe);
1289 15 continue;
1290 15 }
1291
1292 // Reservations are handled immediately and "out of band"
1293
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 2251650 times.
2251860 if (command_type == kReserve) {
1294 210 bool success = true;
1295 const int return_pipe =
1296
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 210 times.
210 if (return_pipe < 0)
1298 continue;
1299
1300
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 const shash::Any hash = command_buffer[num_commands].RetrieveHash();
1301
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 const string hash_str(hash.ToString());
1302
1/2
✓ Branch 2 taken 210 times.
✗ Branch 3 not taken.
210 LogCvmfs(kLogQuota, kLogDebug, "reserve %lu bytes for %s", size,
1303 hash_str.c_str());
1304
1305
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 if (quota_mgr->pinned_chunks_.find(hash)
1306
2/2
✓ Branch 2 taken 180 times.
✓ Branch 3 taken 30 times.
420 == quota_mgr->pinned_chunks_.end()) {
1307
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 165 times.
180 if ((quota_mgr->pinned_ + size) > quota_mgr->cleanup_threshold_) {
1308
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 LogCvmfs(kLogQuota, kLogDebug,
1309 "failed to insert %s (pinned), no space", hash_str.c_str());
1310 15 success = false;
1311 } else {
1312
1/2
✓ Branch 1 taken 165 times.
✗ Branch 2 not taken.
165 quota_mgr->pinned_chunks_[hash] = size;
1313 165 quota_mgr->pinned_ += size;
1314
1/2
✓ Branch 1 taken 165 times.
✗ Branch 2 not taken.
165 quota_mgr->CheckHighPinWatermark();
1315 }
1316 }
1317
1318
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 WritePipe(return_pipe, &success, sizeof(success));
1319
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 quota_mgr->UnbindReturnPipe(return_pipe);
1320 210 continue;
1321 210 }
1322
1323 // Back channels are also handled out of band
1324
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 2251590 times.
2251650 if (command_type == kRegisterBackChannel) {
1325 const int return_pipe =
1326
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1327
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
60 if (return_pipe < 0)
1328 continue;
1329
1330
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 quota_mgr->UnlinkReturnPipe(command_buffer[num_commands].return_pipe);
1331
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 Block2Nonblock(return_pipe); // back channels are opportunistic
1332
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 shash::Md5 hash;
1333 60 memcpy(hash.digest, command_buffer[num_commands].digest,
1334 60 shash::kDigestSizes[shash::kMd5]);
1335
1336 60 quota_mgr->LockBackChannels();
1337 const map<shash::Md5, int>::const_iterator iter =
1338
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 quota_mgr->back_channels_.find(hash);
1339
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
60 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 60 times.
✗ Branch 2 not taken.
60 quota_mgr->back_channels_[hash] = return_pipe;
1345 60 quota_mgr->UnlockBackChannels();
1346
1347 60 char success = 'S';
1348
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 WritePipe(return_pipe, &success, sizeof(success));
1349
1/2
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
60 LogCvmfs(kLogQuota, kLogDebug, "register back channel %s on fd %d",
1350
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
120 hash.ToString().c_str(), return_pipe);
1351
1352 60 continue;
1353 60 }
1354
1355
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 2251560 times.
2251590 if (command_type == kUnregisterBackChannel) {
1356
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 shash::Md5 hash;
1357 30 memcpy(hash.digest, command_buffer[num_commands].digest,
1358 30 shash::kDigestSizes[shash::kMd5]);
1359
1360 30 quota_mgr->LockBackChannels();
1361 const map<shash::Md5, int>::iterator iter =
1362
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 quota_mgr->back_channels_.find(hash);
1363
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 if (iter != quota_mgr->back_channels_.end()) {
1364
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 LogCvmfs(kLogQuota, kLogDebug, "closing back channel %s",
1365
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
60 hash.ToString().c_str());
1366
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 close(iter->second);
1367
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 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 30 quota_mgr->UnlockBackChannels();
1373
1374 30 continue;
1375 30 }
1376
1377 // Unpinnings are also handled immediately with respect to the pinned gauge
1378
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 2251530 times.
2251560 if (command_type == kUnpin) {
1379
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 const shash::Any hash = command_buffer[num_commands].RetrieveHash();
1380
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 const string hash_str(hash.ToString());
1381
1382 const map<shash::Any, uint64_t>::iterator iter =
1383
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 quota_mgr->pinned_chunks_.find(hash);
1384
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 if (iter != quota_mgr->pinned_chunks_.end()) {
1385 30 quota_mgr->pinned_ -= iter->second;
1386
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 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 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
60 if (!FileExists(quota_mgr->cache_dir_ + "/"
1391
4/6
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 15 times.
✓ Branch 10 taken 15 times.
90 + hash.MakePathWithoutSuffix())) {
1392
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 LogCvmfs(kLogQuota, kLogDebug,
1393 "remove orphaned pinned hash %s from cache database",
1394 hash_str.c_str());
1395
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 sqlite3_bind_text(quota_mgr->stmt_size_, 1, &hash_str[0],
1396 15 hash_str.length(), SQLITE_STATIC);
1397 int retval;
1398
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
15 if ((retval = sqlite3_step(quota_mgr->stmt_size_)) == SQLITE_ROW) {
1399 const uint64_t size =
1400
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_column_int64(quota_mgr->stmt_size_, 0);
1401
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 sqlite3_bind_text(quota_mgr->stmt_rm_, 1, &(hash_str[0]),
1402 15 hash_str.length(), SQLITE_STATIC);
1403
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 retval = sqlite3_step(quota_mgr->stmt_rm_);
1404
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
15 if ((retval == SQLITE_DONE) || (retval == SQLITE_OK)) {
1405 15 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 15 times.
✗ Branch 2 not taken.
15 sqlite3_reset(quota_mgr->stmt_rm_);
1411 }
1412
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_reset(quota_mgr->stmt_size_);
1413 }
1414 } else {
1415 LogCvmfs(kLogQuota, kLogDebug, "this chunk was not pinned");
1416 }
1417 30 }
1418
1419 // Immediate commands trigger flushing of the buffer
1420 2251560 const bool immediate_command =
1421
4/4
✓ Branch 0 taken 2251110 times.
✓ Branch 1 taken 315 times.
✓ Branch 2 taken 2250990 times.
✓ Branch 3 taken 120 times.
2251425 (command_type == kCleanup) || (command_type == kList) ||
1422
4/4
✓ Branch 0 taken 2250945 times.
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 2250900 times.
✓ Branch 3 taken 45 times.
2250990 (command_type == kListPinned) || (command_type == kListCatalogs) ||
1423
4/4
✓ Branch 0 taken 2250855 times.
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 2250585 times.
✓ Branch 3 taken 270 times.
2250900 (command_type == kListVolatile) || (command_type == kRemove) ||
1424
4/6
✓ Branch 0 taken 2251425 times.
✓ Branch 1 taken 135 times.
✓ Branch 2 taken 2250585 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2250585 times.
4502985 (command_type == kStatus) || (command_type == kLimits) ||
1425 (command_type == kPid);
1426
2/2
✓ Branch 0 taken 2250585 times.
✓ Branch 1 taken 975 times.
2251560 if (!immediate_command)
1427 2250585 num_commands++;
1428
1429
4/4
✓ Branch 0 taken 2181255 times.
✓ Branch 1 taken 70305 times.
✓ Branch 2 taken 975 times.
✓ Branch 3 taken 2180280 times.
2251560 if ((num_commands == kCommandBufferSize) || immediate_command) {
1430
1/2
✓ Branch 1 taken 71280 times.
✗ Branch 2 not taken.
71280 quota_mgr->ProcessCommandBunch(num_commands, command_buffer,
1431 description_buffer);
1432
2/2
✓ Branch 0 taken 70305 times.
✓ Branch 1 taken 975 times.
71280 if (!immediate_command)
1433 70305 num_commands = 0;
1434 }
1435
1436
2/2
✓ Branch 0 taken 975 times.
✓ Branch 1 taken 2250585 times.
2251560 if (immediate_command) {
1437 // Process cleanup, listings
1438 const int return_pipe =
1439
1/2
✓ Branch 1 taken 975 times.
✗ Branch 2 not taken.
975 quota_mgr->BindReturnPipe(command_buffer[num_commands].return_pipe);
1440
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 975 times.
975 if (return_pipe < 0) {
1441 num_commands = 0;
1442 continue;
1443 }
1444
1445 int retval;
1446 975 sqlite3_stmt *this_stmt_list = NULL;
1447
7/10
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 135 times.
✓ Branch 2 taken 315 times.
✓ Branch 3 taken 120 times.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 45 times.
✓ Branch 6 taken 270 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
975 switch (command_type) {
1448 45 case kRemove: {
1449
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 const shash::Any hash = command_buffer[num_commands].RetrieveHash();
1450
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 const string hash_str = hash.ToString();
1451
1/2
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
45 LogCvmfs(kLogQuota, kLogDebug, "manually removing %s",
1452 hash_str.c_str());
1453 45 bool success = false;
1454
1455
1/2
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
45 sqlite3_bind_text(quota_mgr->stmt_size_, 1, &hash_str[0],
1456 45 hash_str.length(), SQLITE_STATIC);
1457 int retval;
1458
3/4
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 15 times.
45 if ((retval = sqlite3_step(quota_mgr->stmt_size_)) == SQLITE_ROW) {
1459 const uint64_t size =
1460
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 sqlite3_column_int64(quota_mgr->stmt_size_, 0);
1461 const uint64_t is_pinned =
1462
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 sqlite3_column_int64(quota_mgr->stmt_size_, 1);
1463
1464
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 sqlite3_bind_text(quota_mgr->stmt_rm_, 1, &(hash_str[0]),
1465 30 hash_str.length(), SQLITE_STATIC);
1466
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 retval = sqlite3_step(quota_mgr->stmt_rm_);
1467
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
30 if ((retval == SQLITE_DONE) || (retval == SQLITE_OK)) {
1468 30 success = true;
1469 30 quota_mgr->gauge_ -= size;
1470
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 if (is_pinned) {
1471
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 quota_mgr->pinned_chunks_.erase(hash);
1472 15 quota_mgr->pinned_ -= size;
1473 }
1474 } else {
1475 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1476 "failed to delete %s (%d)", hash_str.c_str(), retval);
1477 }
1478
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 sqlite3_reset(quota_mgr->stmt_rm_);
1479 } else {
1480 // File does not exist
1481 15 success = true;
1482 }
1483
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 sqlite3_reset(quota_mgr->stmt_size_);
1484
1485
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 WritePipe(return_pipe, &success, sizeof(success));
1486 45 break;
1487 45 }
1488 135 case kCleanup:
1489
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 retval = quota_mgr->DoCleanup(size);
1490
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 WritePipe(return_pipe, &retval, sizeof(retval));
1491 135 break;
1492 315 case kList:
1493
1/2
✓ Branch 0 taken 315 times.
✗ Branch 1 not taken.
315 if (!this_stmt_list)
1494 315 this_stmt_list = quota_mgr->stmt_list_;
1495 case kListPinned:
1496
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 315 times.
435 if (!this_stmt_list)
1497 120 this_stmt_list = quota_mgr->stmt_list_pinned_;
1498 case kListCatalogs:
1499
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 435 times.
480 if (!this_stmt_list)
1500 45 this_stmt_list = quota_mgr->stmt_list_catalogs_;
1501 case kListVolatile:
1502
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 480 times.
525 if (!this_stmt_list)
1503 45 this_stmt_list = quota_mgr->stmt_list_volatile_;
1504
1505 // Pipe back the list, one by one
1506 int length;
1507
3/4
✓ Branch 1 taken 1501110 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1500585 times.
✓ Branch 4 taken 525 times.
1501110 while (sqlite3_step(this_stmt_list) == SQLITE_ROW) {
1508
1/2
✓ Branch 2 taken 1500585 times.
✗ Branch 3 not taken.
1500585 string path = "(NULL)";
1509
2/4
✓ Branch 1 taken 1500585 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1500585 times.
✗ Branch 4 not taken.
1500585 if (sqlite3_column_type(this_stmt_list, 0) != SQLITE_NULL) {
1510 3001170 path = string(reinterpret_cast<const char *>(
1511
2/4
✓ Branch 1 taken 1500585 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1500585 times.
✗ Branch 5 not taken.
1500585 sqlite3_column_text(this_stmt_list, 0)));
1512 }
1513 1500585 length = path.length();
1514
1/2
✓ Branch 1 taken 1500585 times.
✗ Branch 2 not taken.
1500585 WritePipe(return_pipe, &length, sizeof(length));
1515
1/2
✓ Branch 0 taken 1500585 times.
✗ Branch 1 not taken.
1500585 if (length > 0)
1516
2/4
✓ Branch 1 taken 1500585 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1500585 times.
✗ Branch 5 not taken.
1500585 WritePipe(return_pipe, &path[0], length);
1517 1500585 }
1518 525 length = -1;
1519
1/2
✓ Branch 1 taken 525 times.
✗ Branch 2 not taken.
525 WritePipe(return_pipe, &length, sizeof(length));
1520
1/2
✓ Branch 1 taken 525 times.
✗ Branch 2 not taken.
525 sqlite3_reset(this_stmt_list);
1521 525 break;
1522 270 case kStatus:
1523
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 WritePipe(return_pipe, &quota_mgr->gauge_, sizeof(quota_mgr->gauge_));
1524
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 WritePipe(return_pipe, &quota_mgr->pinned_,
1525 sizeof(quota_mgr->pinned_));
1526 270 break;
1527 case kLimits:
1528 WritePipe(return_pipe, &quota_mgr->limit_, sizeof(quota_mgr->limit_));
1529 WritePipe(return_pipe, &quota_mgr->cleanup_threshold_,
1530 sizeof(quota_mgr->cleanup_threshold_));
1531 break;
1532 case kPid: {
1533 pid_t pid = getpid();
1534 WritePipe(return_pipe, &pid, sizeof(pid));
1535 break;
1536 }
1537 default:
1538 PANIC(NULL); // other types are handled by the bunch processor
1539 }
1540
1/2
✓ Branch 1 taken 975 times.
✗ Branch 2 not taken.
975 quota_mgr->UnbindReturnPipe(return_pipe);
1541 975 num_commands = 0;
1542 }
1543 }
1544
1545
1/2
✓ Branch 1 taken 465 times.
✗ Branch 2 not taken.
465 LogCvmfs(kLogQuota, kLogDebug, "stopping cache manager (%d)", errno);
1546
1/2
✓ Branch 1 taken 465 times.
✗ Branch 2 not taken.
465 close(quota_mgr->pipe_lru_[0]);
1547
1/2
✓ Branch 1 taken 465 times.
✗ Branch 2 not taken.
465 quota_mgr->ProcessCommandBunch(num_commands, command_buffer,
1548 description_buffer);
1549
1550 // Unpin
1551 465 command_buffer[0].command_type = kTouch;
1552 465 for (map<shash::Any, uint64_t>::const_iterator
1553 465 i = quota_mgr->pinned_chunks_.begin(),
1554 465 iEnd = quota_mgr->pinned_chunks_.end();
1555
2/2
✓ Branch 1 taken 150 times.
✓ Branch 2 taken 465 times.
615 i != iEnd;
1556 150 ++i) {
1557
1/2
✓ Branch 2 taken 150 times.
✗ Branch 3 not taken.
150 command_buffer[0].StoreHash(i->first);
1558
1/2
✓ Branch 1 taken 150 times.
✗ Branch 2 not taken.
150 quota_mgr->ProcessCommandBunch(1, command_buffer, description_buffer);
1559 }
1560
1561 465 return NULL;
1562 }
1563
1564
1565 1380 void PosixQuotaManager::MakeReturnPipe(int pipe[2]) {
1566
2/2
✓ Branch 0 taken 1335 times.
✓ Branch 1 taken 45 times.
1380 if (!shared_) {
1567 1335 MakePipe(pipe);
1568 1335 return;
1569 }
1570
1571 // Create FIFO in cache directory, store path name (number) in pipe write end
1572 45 int i = 0;
1573 int retval;
1574 do {
1575
2/4
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 60 times.
✗ Branch 6 not taken.
60 retval = mkfifo((workspace_dir_ + "/pipe" + StringifyInt(i)).c_str(), 0600);
1576 60 pipe[1] = i;
1577 60 i++;
1578
3/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
60 } while ((retval == -1) && (errno == EEXIST));
1579
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
45 assert(retval == 0);
1580
1581 // Connect reader's end
1582
3/6
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 45 times.
✗ Branch 10 not taken.
45 pipe[0] = open((workspace_dir_ + "/pipe" + StringifyInt(pipe[1])).c_str(),
1583 O_RDONLY | O_NONBLOCK);
1584
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
45 assert(pipe[0] >= 0);
1585 45 Nonblock2Block(pipe[0]);
1586 }
1587
1588
1589 2196 void PosixQuotaManager::ParseDirectories(const std::string cache_workspace,
1590 std::string *cache_dir,
1591 std::string *workspace_dir) {
1592
1/2
✓ Branch 1 taken 2196 times.
✗ Branch 2 not taken.
2196 vector<string> dir_tokens(SplitString(cache_workspace, ':'));
1593
2/3
✓ Branch 1 taken 2166 times.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
2196 switch (dir_tokens.size()) {
1594 2166 case 1:
1595
2/4
✓ Branch 2 taken 2166 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2166 times.
✗ Branch 6 not taken.
2166 *cache_dir = *workspace_dir = dir_tokens[0];
1596 2166 break;
1597 30 case 2:
1598
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 *cache_dir = dir_tokens[0];
1599
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 *workspace_dir = dir_tokens[1];
1600 30 break;
1601 default:
1602 PANIC(NULL);
1603 }
1604 2196 }
1605
1606
1607 /**
1608 * Immediately inserts a new pinned catalog. Does cache cleanup if necessary.
1609 *
1610 * \return True on success, false otherwise
1611 */
1612 1451 bool PosixQuotaManager::Pin(const shash::Any &hash,
1613 const uint64_t size,
1614 const string &description,
1615 const bool is_catalog) {
1616
3/4
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1421 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 30 times.
1451 assert((size > 0) || !is_catalog);
1617
1618
1/2
✓ Branch 1 taken 1451 times.
✗ Branch 2 not taken.
1451 const string hash_str = hash.ToString();
1619
1/2
✓ Branch 3 taken 1451 times.
✗ Branch 4 not taken.
1451 LogCvmfs(kLogQuota, kLogDebug, "pin into lru %s, path %s", hash_str.c_str(),
1620 description.c_str());
1621
1622 // Has to run when not yet spawned (cvmfs initialization)
1623
2/2
✓ Branch 0 taken 1241 times.
✓ Branch 1 taken 210 times.
1451 if (!spawned_) {
1624 // Code duplication here
1625
3/4
✓ Branch 2 taken 1241 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 692 times.
✓ Branch 6 taken 549 times.
1241 if (pinned_chunks_.find(hash) == pinned_chunks_.end()) {
1626
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 662 times.
692 if (pinned_ + size > cleanup_threshold_) {
1627
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 LogCvmfs(kLogQuota, kLogDebug, "failed to insert %s (pinned), no space",
1628 hash_str.c_str());
1629 30 return false;
1630 } else {
1631
1/2
✓ Branch 1 taken 662 times.
✗ Branch 2 not taken.
662 pinned_chunks_[hash] = size;
1632 662 pinned_ += size;
1633
1/2
✓ Branch 1 taken 662 times.
✗ Branch 2 not taken.
662 CheckHighPinWatermark();
1634 }
1635 }
1636
1/2
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
1211 const bool exists = Contains(hash_str);
1637
4/4
✓ Branch 0 taken 662 times.
✓ Branch 1 taken 549 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 647 times.
1211 if (!exists && (gauge_ + size > limit_)) {
1638
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 LogCvmfs(kLogQuota, kLogDebug, "over limit, gauge %lu, file size %lu",
1639 gauge_, size);
1640
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const int retval = DoCleanup(cleanup_threshold_);
1641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 assert(retval != 0);
1642 }
1643
1/2
✓ Branch 3 taken 1211 times.
✗ Branch 4 not taken.
1211 sqlite3_bind_text(stmt_new_, 1, &hash_str[0], hash_str.length(),
1644 SQLITE_STATIC);
1645
1/2
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
1211 sqlite3_bind_int64(stmt_new_, 2, size);
1646
1/2
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
1211 sqlite3_bind_int64(stmt_new_, 3, seq_++);
1647
1/2
✓ Branch 3 taken 1211 times.
✗ Branch 4 not taken.
1211 sqlite3_bind_text(stmt_new_, 4, &description[0], description.length(),
1648 SQLITE_STATIC);
1649
3/4
✓ Branch 0 taken 1166 times.
✓ Branch 1 taken 45 times.
✓ Branch 3 taken 1211 times.
✗ Branch 4 not taken.
1211 sqlite3_bind_int64(stmt_new_, 5, is_catalog ? kFileCatalog : kFileRegular);
1650
1/2
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
1211 sqlite3_bind_int64(stmt_new_, 6, 1);
1651
1/2
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
1211 const int retval = sqlite3_step(stmt_new_);
1652
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1211 assert((retval == SQLITE_DONE) || (retval == SQLITE_OK));
1653
1/2
✓ Branch 1 taken 1211 times.
✗ Branch 2 not taken.
1211 sqlite3_reset(stmt_new_);
1654
2/2
✓ Branch 0 taken 662 times.
✓ Branch 1 taken 549 times.
1211 if (!exists)
1655 662 gauge_ += size;
1656 1211 return true;
1657 }
1658
1659 int pipe_reserve[2];
1660
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 MakeReturnPipe(pipe_reserve);
1661
1662 210 LruCommand cmd;
1663 210 cmd.command_type = kReserve;
1664 210 cmd.SetSize(size);
1665
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 cmd.StoreHash(hash);
1666 210 cmd.return_pipe = pipe_reserve[1];
1667
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
1668 bool result;
1669
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 ManagedReadHalfPipe(pipe_reserve[0], &result, sizeof(result));
1670
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 CloseReturnPipe(pipe_reserve);
1671
1672
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 195 times.
210 if (!result)
1673 15 return false;
1674
3/4
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 165 times.
✓ Branch 3 taken 195 times.
✗ Branch 4 not taken.
195 DoInsert(hash, size, description, is_catalog ? kPin : kPinRegular);
1675
1676 195 return true;
1677 1451 }
1678
1679
1680 2166 PosixQuotaManager::PosixQuotaManager(const uint64_t limit,
1681 const uint64_t cleanup_threshold,
1682 2166 const string &cache_workspace)
1683 2166 : shared_(false)
1684 2166 , spawned_(false)
1685 2166 , limit_(limit)
1686 2166 , cleanup_threshold_(cleanup_threshold)
1687 2166 , gauge_(0)
1688 2166 , pinned_(0)
1689 2166 , seq_(0)
1690 2166 , cache_dir_() // initialized in body
1691 2166 , workspace_dir_() // initialized in body
1692 2166 , fd_lock_cachedb_(-1)
1693 2166 , async_delete_(true)
1694 2166 , cachemgr_pid_(0)
1695 2166 , database_(NULL)
1696 2166 , stmt_touch_(NULL)
1697 2166 , stmt_unpin_(NULL)
1698 2166 , stmt_block_(NULL)
1699 2166 , stmt_unblock_(NULL)
1700 2166 , stmt_new_(NULL)
1701 2166 , stmt_lru_(NULL)
1702 2166 , stmt_size_(NULL)
1703 2166 , stmt_rm_(NULL)
1704 2166 , stmt_rm_batch_(NULL)
1705 2166 , stmt_list_(NULL)
1706 2166 , stmt_list_pinned_(NULL)
1707 2166 , stmt_list_catalogs_(NULL)
1708 2166 , stmt_list_volatile_(NULL)
1709 4332 , initialized_(false) {
1710
2/4
✓ Branch 1 taken 2166 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2166 times.
✗ Branch 5 not taken.
2166 ParseDirectories(cache_workspace, &cache_dir_, &workspace_dir_);
1711 2166 pipe_lru_[0] = pipe_lru_[1] = -1;
1712
1/2
✓ Branch 1 taken 2166 times.
✗ Branch 2 not taken.
2166 cleanup_recorder_.AddRecorder(1, 90); // last 1.5 min with second resolution
1713 // last 1.5 h with minute resolution
1714
1/2
✓ Branch 1 taken 2166 times.
✗ Branch 2 not taken.
2166 cleanup_recorder_.AddRecorder(60, 90 * 60);
1715 // last 18 hours with 20 min resolution
1716
1/2
✓ Branch 1 taken 2166 times.
✗ Branch 2 not taken.
2166 cleanup_recorder_.AddRecorder(20 * 60, 60 * 60 * 18);
1717 // last 4 days with hour resolution
1718
1/2
✓ Branch 1 taken 2166 times.
✗ Branch 2 not taken.
2166 cleanup_recorder_.AddRecorder(60 * 60, 60 * 60 * 24 * 4);
1719 2166 }
1720
1721
1722 8660 PosixQuotaManager::~PosixQuotaManager() {
1723
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 2120 times.
4330 if (!initialized_)
1724 90 return;
1725
1726
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2120 times.
4240 if (shared_) {
1727 // Most of cleanup is done elsewhen by shared cache manager
1728 close(pipe_lru_[1]);
1729 return;
1730 }
1731
1732
2/2
✓ Branch 0 taken 465 times.
✓ Branch 1 taken 1655 times.
4240 if (spawned_) {
1733 930 char fin = 0;
1734 930 WritePipe(pipe_lru_[1], &fin, 1);
1735 930 close(pipe_lru_[1]);
1736 930 pthread_join(thread_lru_, NULL);
1737 } else {
1738 3310 ClosePipe(pipe_lru_);
1739 }
1740
1741 4240 CloseDatabase();
1742
10/10
✓ Branch 1 taken 2120 times.
✓ Branch 2 taken 45 times.
✓ Branch 4 taken 2120 times.
✓ Branch 5 taken 45 times.
✓ Branch 7 taken 2120 times.
✓ Branch 8 taken 45 times.
✓ Branch 10 taken 2120 times.
✓ Branch 11 taken 45 times.
✓ Branch 13 taken 2120 times.
✓ Branch 14 taken 45 times.
9020 }
1743
1744
1745 71895 void PosixQuotaManager::ProcessCommandBunch(const unsigned num,
1746 const LruCommand *commands,
1747 const char *descriptions) {
1748 71895 int retval = sqlite3_exec(database_, "BEGIN", NULL, NULL, NULL);
1749
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 71895 times.
71895 assert(retval == SQLITE_OK);
1750
1751
2/2
✓ Branch 0 taken 2250735 times.
✓ Branch 1 taken 71895 times.
2322630 for (unsigned i = 0; i < num; ++i) {
1752
1/2
✓ Branch 1 taken 2250735 times.
✗ Branch 2 not taken.
2250735 const shash::Any hash = commands[i].RetrieveHash();
1753
1/2
✓ Branch 1 taken 2250735 times.
✗ Branch 2 not taken.
2250735 const string hash_str = hash.ToString();
1754 2250735 const unsigned size = commands[i].GetSize();
1755
1/2
✓ Branch 1 taken 2250735 times.
✗ Branch 2 not taken.
2250735 LogCvmfs(kLogQuota, kLogDebug, "processing %s (%d)", hash_str.c_str(),
1756 2250735 commands[i].command_type);
1757
1758 bool exists;
1759
3/4
✓ Branch 0 taken 750195 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1500510 times.
✗ Branch 3 not taken.
2250735 switch (commands[i].command_type) {
1760 750195 case kTouch:
1761
1/2
✓ Branch 1 taken 750195 times.
✗ Branch 2 not taken.
750195 sqlite3_bind_int64(stmt_touch_, 1, seq_++);
1762
1/2
✓ Branch 3 taken 750195 times.
✗ Branch 4 not taken.
750195 sqlite3_bind_text(stmt_touch_, 2, &hash_str[0], hash_str.length(),
1763 SQLITE_STATIC);
1764
1/2
✓ Branch 1 taken 750195 times.
✗ Branch 2 not taken.
750195 retval = sqlite3_step(stmt_touch_);
1765
1/2
✓ Branch 1 taken 750195 times.
✗ Branch 2 not taken.
750195 LogCvmfs(kLogQuota, kLogDebug, "touching %s (%ld): %d",
1766 750195 hash_str.c_str(), seq_ - 1, retval);
1767
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 750195 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
750195 if ((retval != SQLITE_DONE) && (retval != SQLITE_OK)) {
1768 PANIC(kLogSyslogErr, "failed to update %s in cachedb, error %d",
1769 hash_str.c_str(), retval);
1770 }
1771
1/2
✓ Branch 1 taken 750195 times.
✗ Branch 2 not taken.
750195 sqlite3_reset(stmt_touch_);
1772 750195 break;
1773 30 case kUnpin:
1774
1/2
✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
30 sqlite3_bind_text(stmt_unpin_, 1, &hash_str[0], hash_str.length(),
1775 SQLITE_STATIC);
1776
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 retval = sqlite3_step(stmt_unpin_);
1777
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 LogCvmfs(kLogQuota, kLogDebug, "unpinning %s: %d", hash_str.c_str(),
1778 retval);
1779
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
30 if ((retval != SQLITE_DONE) && (retval != SQLITE_OK)) {
1780 PANIC(kLogSyslogErr, "failed to unpin %s in cachedb, error %d",
1781 hash_str.c_str(), retval);
1782 }
1783
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 sqlite3_reset(stmt_unpin_);
1784 30 break;
1785 1500510 case kPin:
1786 case kPinRegular:
1787 case kInsert:
1788 case kInsertVolatile:
1789 // It could already be in, check
1790
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 exists = Contains(hash_str);
1791
1792 // Cleanup, move to trash and unlink
1793
3/4
✓ Branch 0 taken 1500435 times.
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1500435 times.
1500510 if (!exists && (gauge_ + size > limit_)) {
1794 LogCvmfs(kLogQuota, kLogDebug, "over limit, gauge %lu, file size %u",
1795 gauge_, size);
1796 retval = DoCleanup(cleanup_threshold_);
1797 assert(retval != 0);
1798 }
1799
1800 // Insert or replace
1801
1/2
✓ Branch 3 taken 1500510 times.
✗ Branch 4 not taken.
1500510 sqlite3_bind_text(stmt_new_, 1, &hash_str[0], hash_str.length(),
1802 SQLITE_STATIC);
1803
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 sqlite3_bind_int64(stmt_new_, 2, size);
1804
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 1500450 times.
1500510 if (commands[i].command_type == kInsertVolatile) {
1805
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 sqlite3_bind_int64(stmt_new_, 3, (seq_++) | kVolatileFlag);
1806 } else {
1807
1/2
✓ Branch 1 taken 1500450 times.
✗ Branch 2 not taken.
1500450 sqlite3_bind_int64(stmt_new_, 3, seq_++);
1808 }
1809 1500510 sqlite3_bind_text(stmt_new_, 4, &descriptions[i * kMaxDescription],
1810
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 commands[i].desc_length, SQLITE_STATIC);
1811
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 sqlite3_bind_int64(
1812 stmt_new_, 5,
1813
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1500480 times.
1500510 (commands[i].command_type == kPin) ? kFileCatalog : kFileRegular);
1814
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 sqlite3_bind_int64(stmt_new_, 6,
1815
2/2
✓ Branch 0 taken 1500480 times.
✓ Branch 1 taken 30 times.
1500510 ((commands[i].command_type == kPin)
1816
2/2
✓ Branch 0 taken 165 times.
✓ Branch 1 taken 1500315 times.
1500480 || (commands[i].command_type == kPinRegular))
1817 ? 1
1818 : 0);
1819
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 retval = sqlite3_step(stmt_new_);
1820
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 LogCvmfs(kLogQuota, kLogDebug, "insert or replace %s, method %d: %d",
1821 1500510 hash_str.c_str(), commands[i].command_type, retval);
1822
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1500510 if ((retval != SQLITE_DONE) && (retval != SQLITE_OK)) {
1823 PANIC(kLogSyslogErr, "failed to insert %s in cachedb, error %d",
1824 hash_str.c_str(), retval);
1825 }
1826
1/2
✓ Branch 1 taken 1500510 times.
✗ Branch 2 not taken.
1500510 sqlite3_reset(stmt_new_);
1827
1828
2/2
✓ Branch 0 taken 1500435 times.
✓ Branch 1 taken 75 times.
1500510 if (!exists)
1829 1500435 gauge_ += size;
1830 1500510 break;
1831 default:
1832 // other types should have been taken care of by event loop
1833 PANIC(NULL);
1834 }
1835 2250735 }
1836
1837 71895 retval = sqlite3_exec(database_, "COMMIT", NULL, NULL, NULL);
1838
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 71895 times.
71895 if (retval != SQLITE_OK) {
1839 PANIC(kLogSyslogErr, "failed to commit to cachedb, error %d", retval);
1840 }
1841 71895 }
1842
1843
1844 2136 bool PosixQuotaManager::RebuildDatabase() {
1845 2136 bool result = false;
1846 2136 string sql;
1847 2136 sqlite3_stmt *stmt_select = NULL;
1848 2136 sqlite3_stmt *stmt_insert = NULL;
1849 int sqlerr;
1850 2136 int seq = 0;
1851 char hex[4];
1852 struct stat info;
1853 platform_dirent64 *d;
1854 2136 DIR *dirp = NULL;
1855 2136 string path;
1856
1857
1/2
✓ Branch 1 taken 2136 times.
✗ Branch 2 not taken.
2136 LogCvmfs(kLogQuota, kLogSyslog | kLogDebug, "re-building cache database");
1858
1859 // Empty cache catalog and fscache
1860
1/2
✓ Branch 1 taken 2136 times.
✗ Branch 2 not taken.
2136 sql = "DELETE FROM cache_catalog; DELETE FROM fscache;";
1861
1/2
✓ Branch 2 taken 2136 times.
✗ Branch 3 not taken.
2136 sqlerr = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
1862
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2136 times.
2136 if (sqlerr != SQLITE_OK) {
1863 LogCvmfs(kLogQuota, kLogDebug, "could not clear cache database");
1864 goto build_return;
1865 }
1866
1867 2136 gauge_ = 0;
1868
1869 // Insert files from cache sub-directories 00 - ff
1870 // TODO(jblomer): fs_traversal
1871
1/2
✓ Branch 1 taken 2136 times.
✗ Branch 2 not taken.
2136 sqlite3_prepare_v2(database_,
1872 "INSERT INTO fscache (sha1, size, actime) "
1873 "VALUES (:sha1, :s, :t);",
1874 -1, &stmt_insert, NULL);
1875
1876
2/2
✓ Branch 0 taken 535341 times.
✓ Branch 1 taken 2091 times.
537432 for (int i = 0; i <= 0xff; i++) {
1877 535341 snprintf(hex, sizeof(hex), "%02x", i);
1878
3/6
✓ Branch 2 taken 535341 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 535341 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 535341 times.
✗ Branch 9 not taken.
535341 path = cache_dir_ + "/" + string(hex);
1879
3/4
✓ Branch 2 taken 535341 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 535296 times.
535341 if ((dirp = opendir(path.c_str())) == NULL) {
1880
1/2
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
45 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1881 "failed to open directory %s (tmpwatch interfering?)",
1882 path.c_str());
1883 45 goto build_return;
1884 }
1885
3/4
✓ Branch 1 taken 1605918 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1070622 times.
✓ Branch 4 taken 535296 times.
1605918 while ((d = platform_readdir(dirp)) != NULL) {
1886
3/6
✓ Branch 2 taken 1070622 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1070622 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 1070622 times.
✗ Branch 9 not taken.
2141244 const string file_path = path + "/" + string(d->d_name);
1887
1/2
✓ Branch 2 taken 1070622 times.
✗ Branch 3 not taken.
1070622 if (stat(file_path.c_str(), &info) == 0) {
1888
2/2
✓ Branch 0 taken 1070592 times.
✓ Branch 1 taken 30 times.
1070622 if (!S_ISREG(info.st_mode))
1889 1070607 continue;
1890
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 if (info.st_size == 0) {
1891
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 LogCvmfs(kLogQuota, kLogSyslog | kLogDebug,
1892 "removing empty file %s during automatic cache db rebuild",
1893 file_path.c_str());
1894 15 unlink(file_path.c_str());
1895 15 continue;
1896 }
1897
1898
3/6
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 15 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 15 times.
✗ Branch 10 not taken.
30 string hash = string(hex) + string(d->d_name);
1899
1/2
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
15 sqlite3_bind_text(stmt_insert, 1, hash.data(), hash.length(),
1900 SQLITE_STATIC);
1901
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_bind_int64(stmt_insert, 2, info.st_size);
1902
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_bind_int64(stmt_insert, 3, info.st_atime);
1903
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
15 if (sqlite3_step(stmt_insert) != SQLITE_DONE) {
1904 LogCvmfs(kLogQuota, kLogDebug, "could not insert into temp table");
1905 goto build_return;
1906 }
1907
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_reset(stmt_insert);
1908
1909 15 gauge_ += info.st_size;
1910
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 } else {
1911 LogCvmfs(kLogQuota, kLogDebug, "could not stat %s", file_path.c_str());
1912 }
1913
2/3
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 1070607 times.
✗ Branch 3 not taken.
1070622 }
1914
1/2
✓ Branch 1 taken 535296 times.
✗ Branch 2 not taken.
535296 closedir(dirp);
1915 535296 dirp = NULL;
1916 }
1917
1/2
✓ Branch 1 taken 2091 times.
✗ Branch 2 not taken.
2091 sqlite3_finalize(stmt_insert);
1918 2091 stmt_insert = NULL;
1919
1920 // Transfer from temp table in cache catalog
1921
1/2
✓ Branch 1 taken 2091 times.
✗ Branch 2 not taken.
2091 sqlite3_prepare_v2(database_,
1922 "SELECT sha1, size FROM fscache ORDER BY actime;", -1,
1923 &stmt_select, NULL);
1924
1/2
✓ Branch 1 taken 2091 times.
✗ Branch 2 not taken.
2091 sqlite3_prepare_v2(
1925 database_,
1926 "INSERT INTO cache_catalog (sha1, size, acseq, path, type, pinned) "
1927 "VALUES (:sha1, :s, :seq, 'unknown (automatic rebuild)', :t, 0);",
1928 -1, &stmt_insert, NULL);
1929
3/4
✓ Branch 1 taken 2106 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 2091 times.
2106 while (sqlite3_step(stmt_select) == SQLITE_ROW) {
1930 const string hash = string(
1931
2/4
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 reinterpret_cast<const char *>(sqlite3_column_text(stmt_select, 0)));
1932
1/2
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
15 sqlite3_bind_text(stmt_insert, 1, &hash[0], hash.length(), SQLITE_STATIC);
1933
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 sqlite3_bind_int64(stmt_insert, 2, sqlite3_column_int64(stmt_select, 1));
1934
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_bind_int64(stmt_insert, 3, seq++);
1935 // Might also be a catalog (information is lost)
1936
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_bind_int64(stmt_insert, 4, kFileRegular);
1937
1938
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const int retval = sqlite3_step(stmt_insert);
1939
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (retval != SQLITE_DONE) {
1940 // If the file system hosting the cache is full, we'll likely notice here
1941 LogCvmfs(kLogQuota, kLogDebug | kLogSyslogErr,
1942 "could not insert into cache catalog (%d - %s)", retval,
1943 sqlite3_errstr(retval));
1944 goto build_return;
1945 }
1946
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 sqlite3_reset(stmt_insert);
1947
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 }
1948
1949 // Delete temporary table
1950
1/2
✓ Branch 1 taken 2091 times.
✗ Branch 2 not taken.
2091 sql = "DELETE FROM fscache;";
1951
1/2
✓ Branch 2 taken 2091 times.
✗ Branch 3 not taken.
2091 sqlerr = sqlite3_exec(database_, sql.c_str(), NULL, NULL, NULL);
1952
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2091 times.
2091 if (sqlerr != SQLITE_OK) {
1953 LogCvmfs(kLogQuota, kLogDebug, "could not clear temporary table (%d)",
1954 sqlerr);
1955 goto build_return;
1956 }
1957
1958 2091 seq_ = seq;
1959 2091 result = true;
1960
1/2
✓ Branch 1 taken 2091 times.
✗ Branch 2 not taken.
2091 LogCvmfs(kLogQuota, kLogDebug,
1961 "rebuilding finished, sequence %" PRIu64 ", gauge %" PRIu64, seq_,
1962 gauge_);
1963
1964 2136 build_return:
1965
1/2
✓ Branch 0 taken 2136 times.
✗ Branch 1 not taken.
2136 if (stmt_insert)
1966
1/2
✓ Branch 1 taken 2136 times.
✗ Branch 2 not taken.
2136 sqlite3_finalize(stmt_insert);
1967
2/2
✓ Branch 0 taken 2091 times.
✓ Branch 1 taken 45 times.
2136 if (stmt_select)
1968
1/2
✓ Branch 1 taken 2091 times.
✗ Branch 2 not taken.
2091 sqlite3_finalize(stmt_select);
1969
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2136 times.
2136 if (dirp)
1970 closedir(dirp);
1971 2136 return result;
1972 2136 }
1973
1974
1975 /**
1976 * Register a channel that allows the cache manager to trigger action to its
1977 * clients. Currently used for releasing pinned catalogs.
1978 */
1979 60 void PosixQuotaManager::RegisterBackChannel(int back_channel[2],
1980 const string &channel_id) {
1981
1/2
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
60 if (protocol_revision_ >= 1) {
1982
1/2
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
60 shash::Md5 hash = shash::Md5(shash::AsciiPtr(channel_id));
1983
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 MakeReturnPipe(back_channel);
1984
1985 60 LruCommand cmd;
1986 60 cmd.command_type = kRegisterBackChannel;
1987 60 cmd.return_pipe = back_channel[1];
1988 // Not StoreHash(). This is an MD5 hash.
1989 60 memcpy(cmd.digest, hash.digest, hash.GetDigestSize());
1990
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
1991
1992 char success;
1993
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 ManagedReadHalfPipe(back_channel[0], &success, sizeof(success));
1994 // At this point, the named FIFO is unlinked, so don't use CloseReturnPipe
1995
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
60 if (success != 'S') {
1996 PANIC(kLogDebug | kLogSyslogErr,
1997 "failed to register quota back channel (%c)", success);
1998 }
1999 } else {
2000 // Dummy pipe to return valid file descriptors
2001 MakePipe(back_channel);
2002 }
2003 60 }
2004
2005
2006 /**
2007 * Removes a chunk from cache, if it exists.
2008 */
2009 45 void PosixQuotaManager::Remove(const shash::Any &hash) {
2010
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 const string hash_str = hash.ToString();
2011
2012 int pipe_remove[2];
2013
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 MakeReturnPipe(pipe_remove);
2014
2015 45 LruCommand cmd;
2016 45 cmd.command_type = kRemove;
2017 45 cmd.return_pipe = pipe_remove[1];
2018
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 cmd.StoreHash(hash);
2019
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2020
2021 bool success;
2022
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 ManagedReadHalfPipe(pipe_remove[0], &success, sizeof(success));
2023
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 CloseReturnPipe(pipe_remove);
2024
2025
3/6
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 45 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 45 times.
✗ Branch 8 not taken.
45 unlink((cache_dir_ + "/" + hash.MakePathWithoutSuffix()).c_str());
2026 45 }
2027
2028
2029 495 void PosixQuotaManager::Spawn() {
2030
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 465 times.
495 if (spawned_)
2031 30 return;
2032
2033 465 if (pthread_create(&thread_lru_, NULL, MainCommandServer,
2034 static_cast<void *>(this))
2035
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 465 times.
465 != 0) {
2036 PANIC(kLogDebug, "could not create lru thread");
2037 }
2038
2039 465 spawned_ = true;
2040 }
2041
2042
2043 /**
2044 * Updates the sequence number of the file specified by the hash.
2045 */
2046 750726 void PosixQuotaManager::Touch(const shash::Any &hash) {
2047 750726 LruCommand cmd;
2048 750726 cmd.command_type = kTouch;
2049
1/2
✓ Branch 1 taken 750726 times.
✗ Branch 2 not taken.
750726 cmd.StoreHash(hash);
2050
1/2
✓ Branch 1 taken 750726 times.
✗ Branch 2 not taken.
750726 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2051 750726 }
2052
2053
2054 1290 void PosixQuotaManager::UnbindReturnPipe(int pipe_wronly) {
2055
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1275 times.
1290 if (shared_)
2056 15 close(pipe_wronly);
2057 1290 }
2058
2059
2060 105 void PosixQuotaManager::UnlinkReturnPipe(int pipe_wronly) {
2061
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 60 times.
105 if (shared_)
2062
2/4
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
45 unlink((workspace_dir_ + "/pipe" + StringifyInt(pipe_wronly)).c_str());
2063 105 }
2064
2065
2066 860 void PosixQuotaManager::Unpin(const shash::Any &hash) {
2067
2/4
✓ Branch 1 taken 860 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 860 times.
✗ Branch 6 not taken.
860 LogCvmfs(kLogQuota, kLogDebug, "Unpin %s", hash.ToString().c_str());
2068
2069 860 LruCommand cmd;
2070 860 cmd.command_type = kUnpin;
2071
1/2
✓ Branch 1 taken 860 times.
✗ Branch 2 not taken.
860 cmd.StoreHash(hash);
2072
1/2
✓ Branch 1 taken 860 times.
✗ Branch 2 not taken.
860 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2073 860 }
2074
2075
2076 30 void PosixQuotaManager::UnregisterBackChannel(int back_channel[2],
2077 const string &channel_id) {
2078
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 if (protocol_revision_ >= 1) {
2079
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 shash::Md5 hash = shash::Md5(shash::AsciiPtr(channel_id));
2080
2081 30 LruCommand cmd;
2082 30 cmd.command_type = kUnregisterBackChannel;
2083 // Not StoreHash(). This is an MD5 hash.
2084 30 memcpy(cmd.digest, hash.digest, hash.GetDigestSize());
2085
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 WritePipe(pipe_lru_[1], &cmd, sizeof(cmd));
2086
2087 // Writer's end will be closed by cache manager, FIFO is already unlinked
2088
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 close(back_channel[0]);
2089 } else {
2090 ClosePipe(back_channel);
2091 }
2092 30 }
2093
2094 1501905 void PosixQuotaManager::ManagedReadHalfPipe(int fd, void *buf, size_t nbyte) {
2095
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1501905 times.
1501905 const unsigned timeout_ms = cachemgr_pid_ ? 1000 : 0;
2096 1501905 bool result = false;
2097 do {
2098 1501905 result = ReadHalfPipe(fd, buf, nbyte, timeout_ms);
2099 // try only as long as the cachemgr is still alive
2100
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1501905 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1501905 times.
1501905 } while (!result && getpgid(cachemgr_pid_) >= 0);
2101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1501905 times.
1501905 if (!result) {
2102 PANIC(kLogStderr,
2103 "Error: quota manager could not read from cachemanager pipe");
2104 }
2105 1501905 }
2106