GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/quota_posix.cc
Date: 2025-09-14 02:35:40
Exec Total Coverage
Lines: 926 1232 75.2%
Branches: 733 1732 42.3%

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