GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/catalog_mgr_rw.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 477 880 54.2%
Branches: 361 1283 28.1%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM file system.
3 */
4
5 #include "catalog_mgr_rw.h"
6
7 #include <inttypes.h>
8 #include <unistd.h>
9
10 #include <cassert>
11 #include <cstdio>
12 #include <cstdlib>
13 #include <string>
14
15 #include "catalog_balancer.h"
16 #include "catalog_rw.h"
17 #include "manifest.h"
18 #include "statistics.h"
19 #include "upload.h"
20 #include "util/exception.h"
21 #include "util/logging.h"
22 #include "util/posix.h"
23 #include "util/smalloc.h"
24
25 using namespace std; // NOLINT
26
27 namespace catalog {
28
29 817 WritableCatalogManager::WritableCatalogManager(
30 const shash::Any &base_hash,
31 const std::string &stratum0,
32 const string &dir_temp,
33 upload::Spooler *spooler,
34 download::DownloadManager *download_manager,
35 bool enforce_limits,
36 const unsigned nested_kcatalog_limit,
37 const unsigned root_kcatalog_limit,
38 const unsigned file_mbyte_limit,
39 perf::Statistics *statistics,
40 bool is_balanceable,
41 unsigned max_weight,
42 unsigned min_weight,
43 817 const std::string &dir_cache)
44 : SimpleCatalogManager(base_hash, stratum0, dir_temp, download_manager,
45 statistics, false, dir_cache,
46 true /* copy to tmpdir */)
47 817 , spooler_(spooler)
48 817 , enforce_limits_(enforce_limits)
49 817 , nested_kcatalog_limit_(nested_kcatalog_limit)
50 817 , root_kcatalog_limit_(root_kcatalog_limit)
51 817 , file_mbyte_limit_(file_mbyte_limit)
52 817 , is_balanceable_(is_balanceable)
53 817 , max_weight_(max_weight)
54 817 , min_weight_(min_weight)
55 817 , balance_weight_(max_weight / 2) {
56 817 sync_lock_ = reinterpret_cast<pthread_mutex_t *>(
57 817 smalloc(sizeof(pthread_mutex_t)));
58 817 int retval = pthread_mutex_init(sync_lock_, NULL);
59
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 817 times.
817 assert(retval == 0);
60 817 catalog_processing_lock_ = reinterpret_cast<pthread_mutex_t *>(
61 817 smalloc(sizeof(pthread_mutex_t)));
62 817 retval = pthread_mutex_init(catalog_processing_lock_, NULL);
63
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 817 times.
817 assert(retval == 0);
64 817 }
65
66
67 3264 WritableCatalogManager::~WritableCatalogManager() {
68 1632 pthread_mutex_destroy(sync_lock_);
69 1632 free(sync_lock_);
70 1632 pthread_mutex_destroy(catalog_processing_lock_);
71 1632 free(catalog_processing_lock_);
72 3264 }
73
74
75 /**
76 * This method is virtual in AbstractCatalogManager. It returns a new catalog
77 * structure in the form the different CatalogManagers need it.
78 * In this case it returns a stub for a WritableCatalog.
79 * @param mountpoint the mount point of the catalog stub to create
80 * @param catalog_hash the content hash of the catalog to create
81 * @param parent_catalog the parent of the catalog stub to create
82 * @return a pointer to the catalog stub structure created
83 */
84 1673 Catalog *WritableCatalogManager::CreateCatalog(const PathString &mountpoint,
85 const shash::Any &catalog_hash,
86 Catalog *parent_catalog) {
87 3346 return new WritableCatalog(mountpoint.ToString(), catalog_hash,
88
2/4
✓ Branch 1 taken 1673 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1673 times.
✗ Branch 5 not taken.
3346 parent_catalog);
89 }
90
91
92 1673 void WritableCatalogManager::ActivateCatalog(Catalog *catalog) {
93 1673 catalog->TakeDatabaseFileOwnership();
94 1673 }
95
96
97 /**
98 * This method is invoked if we create a completely new repository.
99 * The new root catalog will already contain a root entry.
100 * It is uploaded by a Forklift to the upstream storage.
101 * @return true on success, false otherwise
102 */
103 510 manifest::Manifest *WritableCatalogManager::CreateRepository(
104 const string &dir_temp,
105 const bool volatile_content,
106 const std::string &voms_authz,
107 upload::Spooler *spooler) {
108 // Create a new root catalog at file_path
109
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 const string file_path = dir_temp + "/new_root_catalog";
110
111 510 const shash::Algorithms hash_algorithm = spooler->GetHashAlgorithm();
112
113 // A newly created catalog always needs a root entry
114 // we create and configure this here
115
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 DirectoryEntry root_entry;
116 510 root_entry.inode_ = DirectoryEntry::kInvalidInode;
117 510 root_entry.mode_ = 16877;
118 510 root_entry.size_ = 4096;
119 510 root_entry.mtime_ = time(NULL);
120 510 root_entry.uid_ = getuid();
121 510 root_entry.gid_ = getgid();
122
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 root_entry.checksum_ = shash::Any(hash_algorithm);
123 510 root_entry.linkcount_ = 2;
124
1/2
✓ Branch 2 taken 510 times.
✗ Branch 3 not taken.
510 const string root_path = "";
125
126 // Create the database schema and the initial root entry
127 {
128 const std::unique_ptr<CatalogDatabase> new_clg_db(
129
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 CatalogDatabase::Create(file_path));
130 510 if (new_clg_db.get() == nullptr
131
4/8
✓ Branch 0 taken 510 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 510 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 510 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 510 times.
510 || !new_clg_db->InsertInitialValues(root_path, volatile_content,
132 voms_authz, root_entry)) {
133 ✗ LogCvmfs(kLogCatalog, kLogStderr, "creation of catalog '%s' failed",
134 file_path.c_str());
135 ✗ return NULL;
136 }
137
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 }
138
139 // Compress root catalog;
140
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 const int64_t catalog_size = GetFileSize(file_path);
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 510 times.
510 if (catalog_size < 0) {
142 ✗ unlink(file_path.c_str());
143 ✗ return NULL;
144 }
145
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 const string file_path_compressed = file_path + ".compressed";
146
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 shash::Any hash_catalog(hash_algorithm, shash::kSuffixCatalog);
147
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 const bool retval = zlib::CompressPath2Path(file_path, file_path_compressed,
148 &hash_catalog);
149
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 510 times.
510 if (!retval) {
150 ✗ LogCvmfs(kLogCatalog, kLogStderr, "compression of catalog '%s' failed",
151 file_path.c_str());
152 ✗ unlink(file_path.c_str());
153 ✗ return NULL;
154 }
155 510 unlink(file_path.c_str());
156
157 // Create manifest
158
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 const string manifest_path = dir_temp + "/manifest";
159 manifest::Manifest *manifest = new manifest::Manifest(hash_catalog,
160
3/6
✓ Branch 2 taken 510 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 510 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 510 times.
✗ Branch 9 not taken.
510 catalog_size, "");
161
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 510 times.
510 if (!voms_authz.empty()) {
162 ✗ manifest->set_has_alt_catalog_path(true);
163 }
164
165 // Upload catalog
166
3/6
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 510 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 510 times.
✗ Branch 8 not taken.
510 spooler->Upload(file_path_compressed, "data/" + hash_catalog.MakePath());
167
1/2
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
510 spooler->WaitForUpload();
168 510 unlink(file_path_compressed.c_str());
169
2/4
✓ Branch 1 taken 510 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 510 times.
510 if (spooler->GetNumberOfErrors() > 0) {
170 ✗ LogCvmfs(kLogCatalog, kLogStderr, "failed to commit catalog %s",
171 file_path_compressed.c_str());
172 ✗ delete manifest;
173 ✗ return NULL;
174 }
175
176 510 return manifest;
177 510 }
178
179
180 /**
181 * Retrieve the catalog containing the given path.
182 * Other than AbstractCatalogManager::FindCatalog() this mounts nested
183 * catalogs if necessary and returns WritableCatalog objects.
184 * Furthermore it optionally returns the looked-up DirectoryEntry.
185 *
186 * @param path the path to look for
187 * @param result the retrieved catalog (as a pointer)
188 * @param dirent is set to looked up DirectoryEntry for 'path' if non-NULL
189 * @return true if catalog was found
190 */
191 7937 bool WritableCatalogManager::FindCatalog(const string &path,
192 WritableCatalog **result,
193 DirectoryEntry *dirent) {
194
1/2
✓ Branch 1 taken 7937 times.
✗ Branch 2 not taken.
7937 const PathString ps_path(path);
195
196
1/2
✓ Branch 1 taken 7937 times.
✗ Branch 2 not taken.
7937 Catalog *best_fit = AbstractCatalogManager<Catalog>::FindCatalog(ps_path);
197
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7937 times.
7937 assert(best_fit != NULL);
198 7937 Catalog *catalog = NULL;
199
1/2
✓ Branch 1 taken 7937 times.
✗ Branch 2 not taken.
7937 const bool retval = MountSubtree(ps_path, best_fit, true /* is_listable */,
200 &catalog);
201
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7937 times.
7937 if (!retval)
202 ✗ return false;
203
204 7937 *result = static_cast<WritableCatalog *>(catalog);
205
206
1/2
✓ Branch 1 taken 7937 times.
✗ Branch 2 not taken.
7937 catalog::DirectoryEntry dummy;
207
2/2
✓ Branch 0 taken 4007 times.
✓ Branch 1 taken 3930 times.
7937 if (NULL == dirent) {
208 4007 dirent = &dummy;
209 }
210
1/2
✓ Branch 1 taken 7937 times.
✗ Branch 2 not taken.
7937 const bool found = catalog->LookupPath(ps_path, dirent);
211
6/8
✓ Branch 0 taken 7924 times.
✓ Branch 1 taken 13 times.
✓ Branch 3 taken 7924 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 7924 times.
✓ Branch 7 taken 13 times.
✓ Branch 8 taken 7924 times.
7937 if (!found || !catalog->IsWritable())
212 13 return false;
213
214 7924 return true;
215 7937 }
216
217
218 ✗ WritableCatalog *WritableCatalogManager::GetHostingCatalog(
219 const std::string &path) {
220 ✗ WritableCatalog *result = NULL;
221 ✗ const bool retval = FindCatalog(MakeRelativePath(path), &result, NULL);
222 ✗ if (!retval)
223 ✗ return NULL;
224 ✗ return result;
225 }
226
227
228 /**
229 * Remove the given file from the catalogs.
230 * @param file_path the full path to the file to be removed
231 * @return true on success, false otherwise
232 */
233 31 void WritableCatalogManager::RemoveFile(const std::string &path) {
234
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 const string file_path = MakeRelativePath(path);
235
236 31 SyncLock();
237 31 WritableCatalog *catalog = NULL;
238
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 DirectoryEntry entry;
239
2/4
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
✗ Branch 4 not taken.
31 if (FindCatalog(file_path, &catalog, &entry)) {
240
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
31 if (entry.IsBundleTrigger()) {
241 ✗ catalog->RemoveEntry(GetParentPath(file_path) + "/.cvmfsbundle-"
242 ✗ + GetFileName(file_path));
243 }
244
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 catalog->RemoveEntry(file_path);
245 }
246 31 SyncUnlock();
247
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 if (catalog == NULL) {
249 ✗ PANIC(kLogStderr, "catalog for file '%s' cannot be found",
250 file_path.c_str());
251 }
252 31 }
253
254
255 /**
256 * Remove the given directory from the catalogs.
257 * @param directory_path the full path to the directory to be removed
258 * @return true on success, false otherwise
259 */
260 51 void WritableCatalogManager::RemoveDirectory(const std::string &path) {
261
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 const string directory_path = MakeRelativePath(path);
262
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 const string parent_path = GetParentPath(directory_path);
263
264 51 SyncLock();
265 WritableCatalog *catalog;
266
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 DirectoryEntry parent_entry;
267
2/4
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 51 times.
51 if (!FindCatalog(parent_path, &catalog, &parent_entry)) {
268 ✗ PANIC(kLogStderr, "catalog for directory '%s' cannot be found",
269 directory_path.c_str());
270 }
271
272 51 parent_entry.set_linkcount(parent_entry.linkcount() - 1);
273
274
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 catalog->RemoveEntry(directory_path);
275
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 catalog->UpdateEntry(parent_entry, parent_path);
276
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 25 times.
51 if (parent_entry.IsNestedCatalogRoot()) {
277
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating transition point %s",
278 parent_path.c_str());
279 WritableCatalog *parent_catalog = reinterpret_cast<WritableCatalog *>(
280 26 catalog->parent());
281 26 parent_entry.set_is_nested_catalog_mountpoint(true);
282 26 parent_entry.set_is_nested_catalog_root(false);
283
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 parent_catalog->UpdateEntry(parent_entry, parent_path);
284 }
285 51 SyncUnlock();
286 51 }
287
288 /**
289 * Clone the file called `source` changing its name into `destination`, the
290 * source file is keep intact.
291 * @params destination, the name of the new file, complete path
292 * @params source, the name of the file to clone, which must be already in the
293 * repository
294 * @params fail_if_source_missing, when true (default) abort if the source is
295 * not in the catalog; when false, skip the clone and return false instead.
296 * This is used by the tarball ingestion engine to tolerate hardlinks whose
297 * target is not part of the same archive (e.g. cross-layer hardlinks in OCI
298 * image layers).
299 * @return true if the file was cloned, false if the source was missing and
300 * fail_if_source_missing was false
301 */
302 ✗ bool WritableCatalogManager::Clone(const std::string destination,
303 const std::string source,
304 const bool fail_if_source_missing) {
305 ✗ const std::string relative_source = MakeRelativePath(source);
306
307 ✗ DirectoryEntry source_dirent;
308 ✗ if (!LookupPath(relative_source, kLookupDefault, &source_dirent)) {
309 ✗ if (fail_if_source_missing) {
310 ✗ PANIC(kLogStderr, "catalog for file '%s' cannot be found, aborting",
311 source.c_str());
312 }
313 // The caller opted out of aborting and is expected to handle the missing
314 // source (e.g. by materializing a replacement file), so only log at debug
315 // level here to avoid duplicate user-facing warnings.
316 ✗ LogCvmfs(kLogCatalog, kLogDebug,
317 "catalog for clone source '%s' cannot be found, not cloning "
318 "to '%s'",
319 source.c_str(), destination.c_str());
320 ✗ return false;
321 }
322 ✗ if (source_dirent.IsDirectory()) {
323 ✗ PANIC(kLogStderr, "Trying to clone a directory: '%s', aborting",
324 source.c_str());
325 }
326
327 // if the file is already there we remove it and we add it back
328 ✗ DirectoryEntry check_dirent;
329 ✗ const bool destination_already_present = LookupPath(
330 ✗ MakeRelativePath(destination), kLookupDefault, &check_dirent);
331 ✗ if (destination_already_present) {
332 ✗ this->RemoveFile(destination);
333 }
334
335 ✗ DirectoryEntry destination_dirent(source_dirent);
336 ✗ std::string destination_dirname;
337 ✗ std::string destination_filename;
338 ✗ SplitPath(destination, &destination_dirname, &destination_filename);
339
340 ✗ destination_dirent.name_.Assign(
341 ✗ NameString(destination_filename.c_str(), destination_filename.length()));
342
343 // TODO(jblomer): clone is used by tarball engine and should eventually
344 // support extended attributes
345 ✗ this->AddFile(destination_dirent, empty_xattrs, destination_dirname);
346 ✗ return true;
347 }
348
349
350 /**
351 * Copies an entire directory tree from the existing from_dir to the
352 * non-existing to_dir. The destination's parent directory must exist. On the
353 * catalog level, the new entries will be identical to the old ones except
354 * for their path hash fields.
355 */
356 130 void WritableCatalogManager::CloneTree(const std::string &from_dir,
357 const std::string &to_dir) {
358 // Sanitize input paths
359
6/6
✓ Branch 1 taken 104 times.
✓ Branch 2 taken 26 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 91 times.
✓ Branch 6 taken 39 times.
✓ Branch 7 taken 91 times.
130 if (from_dir.empty() || to_dir.empty())
360 39 PANIC(kLogStderr, "clone tree from or to root impossible");
361
362
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 const std::string relative_source = MakeRelativePath(from_dir);
363
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 const std::string relative_dest = MakeRelativePath(to_dir);
364
365
2/2
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 78 times.
91 if (relative_source == relative_dest) {
366 13 PANIC(kLogStderr, "cannot clone tree into itself ('%s')", to_dir.c_str());
367 }
368
4/7
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 78 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 13 times.
✓ Branch 8 taken 65 times.
78 if (HasPrefix(relative_dest, relative_source + "/", false /*ignore_case*/)) {
369 13 PANIC(kLogStderr,
370 "cannot clone tree into sub directory of source '%s' --> '%s'",
371 from_dir.c_str(), to_dir.c_str());
372 }
373
374
1/2
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
65 DirectoryEntry source_dirent;
375
3/4
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 52 times.
65 if (!LookupPath(relative_source, kLookupDefault, &source_dirent)) {
376 13 PANIC(kLogStderr, "path '%s' cannot be found, aborting", from_dir.c_str());
377 }
378
2/2
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 39 times.
52 if (!source_dirent.IsDirectory()) {
379 13 PANIC(kLogStderr, "CloneTree: source '%s' not a directory, aborting",
380 from_dir.c_str());
381 }
382
383
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 DirectoryEntry dest_dirent;
384
3/4
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 26 times.
39 if (LookupPath(relative_dest, kLookupDefault, &dest_dirent)) {
385 13 PANIC(kLogStderr, "destination '%s' exists, aborting", to_dir.c_str());
386 }
387
388
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 const std::string dest_parent = GetParentPath(relative_dest);
389
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 DirectoryEntry dest_parent_dirent;
390
3/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 13 times.
26 if (!LookupPath(dest_parent, kLookupDefault, &dest_parent_dirent)) {
391 13 PANIC(kLogStderr, "destination '%s' not on a known path, aborting",
392 to_dir.c_str());
393 }
394
395
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 CloneTreeImpl(PathString(from_dir),
396
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
26 GetParentPath(to_dir),
397
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
26 NameString(GetFileName(to_dir)));
398 273 }
399
400
401 /**
402 * Called from CloneTree(), assumes that from_dir and to_dir are sufficiently
403 * sanitized
404 */
405 104 void WritableCatalogManager::CloneTreeImpl(const PathString &source_dir,
406 const std::string &dest_parent_dir,
407 const NameString &dest_name) {
408
1/2
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
104 LogCvmfs(kLogCatalog, kLogDebug, "cloning %s --> %s/%s", source_dir.c_str(),
409
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
208 dest_parent_dir.c_str(), dest_name.ToString().c_str());
410
3/6
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 104 times.
✗ Branch 8 not taken.
208 const PathString relative_source(MakeRelativePath(source_dir.ToString()));
411
412
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 DirectoryEntry source_dirent;
413
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 bool retval = LookupPath(relative_source, kLookupDefault, &source_dirent);
414
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(retval);
415
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
104 assert(!source_dirent.IsBindMountpoint());
416
417
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 DirectoryEntry dest_dirent(source_dirent);
418
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 dest_dirent.name_.Assign(dest_name);
419 // Just in case, reset the nested catalog markers
420 104 dest_dirent.set_is_nested_catalog_mountpoint(false);
421 104 dest_dirent.set_is_nested_catalog_root(false);
422
423 104 XattrList xattrs;
424
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
104 if (source_dirent.HasXattrs()) {
425 ✗ retval = LookupXattrs(relative_source, &xattrs);
426 ✗ assert(retval);
427 }
428
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 AddDirectory(dest_dirent, xattrs, dest_parent_dir);
429
430
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 std::string dest_dir = dest_parent_dir;
431
2/2
✓ Branch 1 taken 91 times.
✓ Branch 2 taken 13 times.
104 if (!dest_dir.empty())
432
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 dest_dir.push_back('/');
433
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
104 dest_dir += dest_name.ToString();
434 104 if (source_dirent.IsNestedCatalogRoot()
435
5/6
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 39 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 65 times.
✓ Branch 5 taken 39 times.
✓ Branch 6 taken 65 times.
104 || source_dirent.IsNestedCatalogMountpoint()) {
436
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 CreateNestedCatalog(dest_dir);
437 }
438
439 104 DirectoryEntryList ls;
440
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 retval = Listing(relative_source, &ls, false /* expand_symlink */);
441
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(retval);
442
2/2
✓ Branch 1 taken 221 times.
✓ Branch 2 taken 104 times.
325 for (unsigned i = 0; i < ls.size(); ++i) {
443
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 PathString sub_path(source_dir);
444
2/4
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 221 times.
221 assert(!sub_path.IsEmpty());
445
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 sub_path.Append("/", 1);
446
3/6
✓ Branch 2 taken 221 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 221 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 221 times.
✗ Branch 12 not taken.
221 sub_path.Append(ls[i].name().GetChars(), ls[i].name().GetLength());
447
448
2/2
✓ Branch 2 taken 91 times.
✓ Branch 3 taken 130 times.
221 if (ls[i].IsDirectory()) {
449
2/4
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 91 times.
✗ Branch 6 not taken.
91 CloneTreeImpl(sub_path, dest_dir, ls[i].name());
450 91 continue;
451 }
452
453 // We break hard-links during cloning
454 130 ls[i].set_hardlink_group(0);
455 130 ls[i].set_linkcount(1);
456
457 130 xattrs.Clear();
458
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 130 times.
130 if (ls[i].HasXattrs()) {
459 ✗ retval = LookupXattrs(sub_path, &xattrs);
460 ✗ assert(retval);
461 }
462
463
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 130 times.
130 if (ls[i].IsChunkedFile()) {
464 ✗ FileChunkList chunks;
465 const std::string relative_sub_path = MakeRelativePath(
466 ✗ sub_path.ToString());
467 ✗ retval = ListFileChunks(PathString(relative_sub_path),
468 ✗ ls[i].hash_algorithm(), &chunks);
469 ✗ assert(retval);
470 ✗ AddChunkedFile(ls[i], xattrs, dest_dir, chunks);
471 ✗ } else {
472
1/2
✓ Branch 2 taken 130 times.
✗ Branch 3 not taken.
130 AddFile(ls[i], xattrs, dest_dir);
473 }
474
2/2
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 91 times.
221 }
475 104 }
476
477
478 /**
479 * Add a new directory to the catalogs.
480 * @param entry a DirectoryEntry structure describing the new directory
481 * @param parent_directory the absolute path of the directory containing the
482 * directory to be created
483 * @return true on success, false otherwise
484 */
485 3132 void WritableCatalogManager::AddDirectory(const DirectoryEntryBase &entry,
486 const XattrList &xattrs,
487 const std::string &parent_directory) {
488
1/2
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
3132 const string parent_path = MakeRelativePath(parent_directory);
489
1/2
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
3132 string directory_path = parent_path + "/";
490
3/6
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 3132 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 3132 times.
✗ Branch 10 not taken.
3132 directory_path.append(entry.name().GetChars(), entry.name().GetLength());
491
492 3132 SyncLock();
493 WritableCatalog *catalog;
494
1/2
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
3132 DirectoryEntry parent_entry;
495
2/4
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3132 times.
3132 if (!FindCatalog(parent_path, &catalog, &parent_entry)) {
496 ✗ PANIC(kLogStderr, "catalog for directory '%s' cannot be found",
497 directory_path.c_str());
498 }
499
500
1/2
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
3132 DirectoryEntry fixed_hardlink_count(entry);
501 3132 fixed_hardlink_count.set_linkcount(2);
502
1/2
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
3132 catalog->AddEntry(fixed_hardlink_count, xattrs, directory_path, parent_path);
503
504 3132 parent_entry.set_linkcount(parent_entry.linkcount() + 1);
505
1/2
✓ Branch 1 taken 3132 times.
✗ Branch 2 not taken.
3132 catalog->UpdateEntry(parent_entry, parent_path);
506
2/2
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 3093 times.
3132 if (parent_entry.IsNestedCatalogRoot()) {
507
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating transition point %s",
508 parent_path.c_str());
509 WritableCatalog *parent_catalog = reinterpret_cast<WritableCatalog *>(
510 39 catalog->parent());
511 39 parent_entry.set_is_nested_catalog_mountpoint(true);
512 39 parent_entry.set_is_nested_catalog_root(false);
513
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 parent_catalog->UpdateEntry(parent_entry, parent_path);
514 }
515 3132 SyncUnlock();
516 3132 }
517
518 /**
519 * Add a new file to the catalogs.
520 * @param entry a DirectoryEntry structure describing the new file
521 * @param parent_directory the absolute path of the directory containing the
522 * file to be created
523 * @return true on success, false otherwise
524 */
525 3881 void WritableCatalogManager::AddFile(const DirectoryEntry &entry,
526 const XattrList &xattrs,
527 const std::string &parent_directory) {
528
1/2
✓ Branch 1 taken 3881 times.
✗ Branch 2 not taken.
3881 const string parent_path = MakeRelativePath(parent_directory);
529
1/2
✓ Branch 1 taken 3881 times.
✗ Branch 2 not taken.
3881 const string file_path = entry.GetFullPath(parent_path);
530
531 3881 SyncLock();
532 WritableCatalog *catalog;
533
2/4
✓ Branch 1 taken 3881 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3881 times.
3881 if (!FindCatalog(parent_path, &catalog)) {
534 ✗ PANIC(kLogStderr, "catalog for file '%s' cannot be found",
535 file_path.c_str());
536 }
537
538
4/6
✓ Branch 1 taken 3821 times.
✓ Branch 2 taken 60 times.
✓ Branch 4 taken 3821 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3821 times.
3881 assert(!entry.IsRegular() || entry.IsChunkedFile()
539 || !entry.checksum().IsNull());
540
3/4
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 3821 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 60 times.
3881 assert(entry.IsRegular() || !entry.IsExternalFile());
541
542 // check if file is too big
543
1/2
✓ Branch 1 taken 3881 times.
✗ Branch 2 not taken.
3881 const unsigned mbytes = entry.size() / (1024 * 1024);
544
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3881 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3881 if ((file_mbyte_limit_ > 0) && (mbytes > file_mbyte_limit_)) {
545 ✗ LogCvmfs(kLogCatalog, kLogStderr,
546 "%s: file at %s is larger than %u megabytes (%u). "
547 "CernVM-FS works best with small files. "
548 "Please remove the file or increase the limit.",
549 ✗ enforce_limits_ ? "FATAL" : "WARNING", file_path.c_str(),
550 file_mbyte_limit_, mbytes);
551 ✗ if (enforce_limits_)
552 ✗ PANIC(kLogStderr, "file at %s is larger than %u megabytes (%u).",
553 file_path.c_str(), file_mbyte_limit_, mbytes);
554 }
555
556
1/2
✓ Branch 1 taken 3881 times.
✗ Branch 2 not taken.
3881 catalog->AddEntry(entry, xattrs, file_path, parent_path);
557 3881 SyncUnlock();
558 3881 }
559
560
561 ✗ void WritableCatalogManager::AddChunkedFile(const DirectoryEntryBase &entry,
562 const XattrList &xattrs,
563 const std::string &parent_directory,
564 const FileChunkList &file_chunks) {
565 ✗ assert(file_chunks.size() > 0);
566
567 ✗ DirectoryEntry full_entry(entry);
568 ✗ full_entry.set_is_chunked_file(true);
569
570 ✗ AddFile(full_entry, xattrs, parent_directory);
571
572 ✗ const string parent_path = MakeRelativePath(parent_directory);
573 ✗ const string file_path = entry.GetFullPath(parent_path);
574
575 ✗ SyncLock();
576 WritableCatalog *catalog;
577 ✗ if (!FindCatalog(parent_path, &catalog)) {
578 ✗ PANIC(kLogStderr, "catalog for file '%s' cannot be found",
579 file_path.c_str());
580 }
581
582 ✗ for (unsigned i = 0; i < file_chunks.size(); ++i) {
583 ✗ catalog->AddFileChunk(file_path, *file_chunks.AtPtr(i));
584 }
585 ✗ SyncUnlock();
586 }
587
588 /**
589 * Reads the entry given by file_path and sets the bundle trigger flag
590 * accordingly. If new_value is true, the regular file at file_path must exist.
591 * If new_value is false, a missing file_path is ignored (otherwise it would
592 * be hard to get rid of dangling bundle trigger markers).
593 */
594 ✗ void WritableCatalogManager::UpdateBundleTrigger(const std::string &file_path,
595 bool new_value) {
596 ✗ SyncLock();
597
598 ✗ WritableCatalog *catalog = NULL;
599 ✗ DirectoryEntry entry;
600 ✗ if (!FindCatalog(file_path, &catalog, &entry)) {
601 ✗ SyncUnlock();
602
603 ✗ if (new_value) {
604 ✗ PANIC(kLogStderr, "failed to find catalog of %s", file_path.c_str());
605 }
606
607 ✗ LogCvmfs(kLogCatalog, kLogDebug, "dangling bundle trigger %s",
608 file_path.c_str());
609 ✗ return;
610 }
611
612 ✗ if (new_value && !entry.IsRegular()) {
613 ✗ SyncUnlock();
614 ✗ PANIC(kLogStderr, "failed to set bundle trigger on non-regular file %s",
615 file_path.c_str());
616 }
617
618 ✗ entry.set_is_bundle_trigger(new_value);
619 ✗ catalog->UpdateEntry(entry, file_path);
620
621 ✗ SyncUnlock();
622 }
623
624
625 /**
626 * Add a hardlink group to the catalogs.
627 * @param entries a list of DirectoryEntries describing the new files
628 * @param parent_directory the absolute path of the directory containing the
629 * files to be created
630 * @return true on success, false otherwise
631 */
632 ✗ void WritableCatalogManager::AddHardlinkGroup(
633 const DirectoryEntryBaseList &entries,
634 const XattrList &xattrs,
635 const std::string &parent_directory,
636 const FileChunkList &file_chunks) {
637 ✗ assert(entries.size() >= 1);
638 ✗ assert(file_chunks.IsEmpty() || entries[0].IsRegular());
639 ✗ if (entries.size() == 1) {
640 ✗ DirectoryEntry fix_linkcount(entries[0]);
641 ✗ fix_linkcount.set_linkcount(1);
642 ✗ if (file_chunks.IsEmpty())
643 ✗ return AddFile(fix_linkcount, xattrs, parent_directory);
644 ✗ return AddChunkedFile(fix_linkcount, xattrs, parent_directory, file_chunks);
645 }
646
647 ✗ LogCvmfs(kLogCatalog, kLogVerboseMsg, "adding hardlink group %s/%s",
648 ✗ parent_directory.c_str(), entries[0].name().c_str());
649
650 // Hardlink groups have to reside in the same directory.
651 // Therefore we only have one parent directory here
652 ✗ const string parent_path = MakeRelativePath(parent_directory);
653
654 // check if hard link is too big
655 ✗ const unsigned mbytes = entries[0].size() / (1024 * 1024);
656 ✗ if ((file_mbyte_limit_ > 0) && (mbytes > file_mbyte_limit_)) {
657 ✗ LogCvmfs(kLogCatalog, kLogStderr,
658 "%s: hard link at %s is larger than %u megabytes (%u). "
659 "CernVM-FS works best with small files. "
660 "Please remove the file or increase the limit.",
661 ✗ enforce_limits_ ? "FATAL" : "WARNING",
662 ✗ (parent_path + entries[0].name().ToString()).c_str(),
663 file_mbyte_limit_, mbytes);
664 ✗ if (enforce_limits_)
665 ✗ PANIC(kLogStderr, "hard link at %s is larger than %u megabytes (%u)",
666 (parent_path + entries[0].name().ToString()).c_str(),
667 file_mbyte_limit_, mbytes);
668 }
669
670 ✗ SyncLock();
671 WritableCatalog *catalog;
672 ✗ if (!FindCatalog(parent_path, &catalog)) {
673 ✗ PANIC(kLogStderr,
674 "catalog for hardlink group containing '%s' cannot be found",
675 parent_path.c_str());
676 }
677
678 // Get a valid hardlink group id for the catalog the group will end up in
679 // TODO(unknown): Compaction
680 ✗ const uint32_t new_group_id = catalog->GetMaxLinkId() + 1;
681 ✗ LogCvmfs(kLogCatalog, kLogVerboseMsg, "hardlink group id %u issued",
682 new_group_id);
683 ✗ assert(new_group_id > 0);
684
685 // Add the file entries to the catalog
686 ✗ for (DirectoryEntryBaseList::const_iterator i = entries.begin(),
687 ✗ iEnd = entries.end();
688 ✗ i != iEnd;
689 ✗ ++i) {
690 ✗ string file_path = parent_path + "/";
691 ✗ file_path.append(i->name().GetChars(), i->name().GetLength());
692
693 // create a fully fledged DirectoryEntry to add the hardlink group to it
694 // which is CVMFS specific meta data.
695 ✗ DirectoryEntry hardlink(*i);
696 ✗ hardlink.set_hardlink_group(new_group_id);
697 ✗ hardlink.set_linkcount(entries.size());
698 ✗ hardlink.set_is_chunked_file(!file_chunks.IsEmpty());
699
700 ✗ catalog->AddEntry(hardlink, xattrs, file_path, parent_path);
701 ✗ if (hardlink.IsChunkedFile()) {
702 ✗ for (unsigned i = 0; i < file_chunks.size(); ++i) {
703 ✗ catalog->AddFileChunk(file_path, *file_chunks.AtPtr(i));
704 }
705 }
706 }
707 ✗ SyncUnlock();
708 }
709
710
711 ✗ void WritableCatalogManager::ShrinkHardlinkGroup(const string &remove_path) {
712 ✗ const string relative_path = MakeRelativePath(remove_path);
713
714 ✗ SyncLock();
715 WritableCatalog *catalog;
716 ✗ if (!FindCatalog(relative_path, &catalog)) {
717 ✗ PANIC(kLogStderr,
718 "catalog for hardlink group containing '%s' cannot be found",
719 remove_path.c_str());
720 }
721
722 ✗ catalog->IncLinkcount(relative_path, -1);
723 ✗ SyncUnlock();
724 }
725
726
727 /**
728 * Update entry meta data (mode, owner, ...).
729 * CVMFS specific meta data (i.e. nested catalog transition points) are NOT
730 * changed by this method, although transition points intrinsics are taken into
731 * account, to keep nested catalogs consistent.
732 * @param entry the directory entry to be touched
733 * @param path the path of the directory entry to be touched
734 */
735 6 void WritableCatalogManager::TouchDirectory(const DirectoryEntryBase &entry,
736 const XattrList &xattrs,
737 const std::string &directory_path) {
738
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 assert(entry.IsDirectory());
739
740
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const string entry_path = MakeRelativePath(directory_path);
741
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const string parent_path = GetParentPath(entry_path);
742
743 6 SyncLock();
744 // find the catalog to be updated
745 WritableCatalog *catalog;
746
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if (!FindCatalog(parent_path, &catalog)) {
747 ✗ PANIC(kLogStderr, "catalog for entry '%s' cannot be found",
748 entry_path.c_str());
749 }
750
751
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 catalog->TouchEntry(entry, xattrs, entry_path);
752
753 // since we deal with a directory here, we might just touch a
754 // nested catalog transition point. If this is the case we would need to
755 // update two catalog entries:
756 // * the nested catalog MOUNTPOINT in the parent catalog
757 // * the nested catalog ROOT in the nested catalog
758
759 // first check if we really have a nested catalog transition point
760
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 catalog::DirectoryEntry potential_transition_point;
761
1/2
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 const PathString transition_path(entry_path.data(), entry_path.length());
762
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 bool retval = catalog->LookupPath(transition_path,
763 &potential_transition_point);
764
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 assert(retval);
765
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if (potential_transition_point.IsNestedCatalogMountpoint()) {
766 ✗ LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating transition point at %s",
767 entry_path.c_str());
768
769 // find and mount nested catalog associated to this transition point
770 ✗ shash::Any nested_hash;
771 uint64_t nested_size;
772 ✗ retval = catalog->FindNested(transition_path, &nested_hash, &nested_size);
773 ✗ assert(retval);
774 Catalog *nested_catalog;
775 ✗ nested_catalog = MountCatalog(transition_path, nested_hash, catalog);
776 ✗ assert(nested_catalog != NULL);
777
778 // update nested catalog root in the child catalog
779 reinterpret_cast<WritableCatalog *>(nested_catalog)
780 ✗ ->TouchEntry(entry, xattrs, entry_path);
781 }
782
783 6 SyncUnlock();
784 6 }
785
786
787 /**
788 * Create a new nested catalog. Includes moving all entries belonging there
789 * from it's parent catalog.
790 * @param mountpoint the path of the directory to become a nested root
791 * @return true on success, false otherwise
792 */
793 690 void WritableCatalogManager::CreateNestedCatalog(
794 const std::string &mountpoint) {
795
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 const string nested_root_path = MakeRelativePath(mountpoint);
796
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 const PathString ps_nested_root_path(nested_root_path);
797
798 690 SyncLock();
799 // Find the catalog currently containing the directory structure, which
800 // will be represented as a new nested catalog and its root-entry/mountpoint
801 // along the way
802 690 WritableCatalog *old_catalog = NULL;
803
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 DirectoryEntry new_root_entry;
804
2/4
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 690 times.
690 if (!FindCatalog(nested_root_path, &old_catalog, &new_root_entry)) {
805 ✗ PANIC(kLogStderr,
806 "failed to create nested catalog '%s': "
807 "mountpoint was not found in current catalog structure",
808 nested_root_path.c_str());
809 }
810
811 // Create the database schema and the initial root entry
812 // for the new nested catalog
813
1/2
✓ Branch 2 taken 690 times.
✗ Branch 3 not taken.
690 const string database_file_path = CreateTempPath(dir_temp() + "/catalog",
814
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 0666);
815 690 const bool volatile_content = false;
816
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 CatalogDatabase *new_catalog_db = CatalogDatabase::Create(database_file_path);
817
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 690 times.
690 assert(NULL != new_catalog_db);
818 // Note we do not set the external_data bit for nested catalogs
819
2/4
✓ Branch 2 taken 690 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 690 times.
✗ Branch 6 not taken.
690 bool retval = new_catalog_db->InsertInitialValues(
820 nested_root_path,
821 volatile_content,
822 "", // At this point, only root
823 // catalog gets VOMS authz
824 new_root_entry);
825
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 690 times.
690 assert(retval);
826 // TODO(rmeusel): we need a way to attach a catalog directly from an open
827 // database to remove this indirection
828
1/2
✓ Branch 0 taken 690 times.
✗ Branch 1 not taken.
690 delete new_catalog_db;
829 690 new_catalog_db = NULL;
830
831 // Attach the just created nested catalog
832
2/4
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 690 times.
✗ Branch 5 not taken.
690 Catalog *new_catalog = CreateCatalog(ps_nested_root_path, shash::Any(),
833 old_catalog);
834
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 retval = AttachCatalog(database_file_path, new_catalog);
835
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 690 times.
690 assert(retval);
836
837
2/4
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 690 times.
690 assert(new_catalog->IsWritable());
838 690 WritableCatalog *wr_new_catalog = static_cast<WritableCatalog *>(new_catalog);
839
840
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 690 times.
690 if (new_root_entry.HasXattrs()) {
841 ✗ XattrList xattrs;
842 ✗ retval = old_catalog->LookupXattrsPath(ps_nested_root_path, &xattrs);
843 ✗ assert(retval);
844 ✗ wr_new_catalog->TouchEntry(new_root_entry, xattrs, nested_root_path);
845 }
846
847 // From now on, there are two catalogs, spanning the same directory structure
848 // we have to split the overlapping directory entries from the old catalog
849 // to the new catalog to re-gain a valid catalog structure
850
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 old_catalog->Partition(wr_new_catalog);
851
852 // Add the newly created nested catalog to the references of the containing
853 // catalog
854
4/8
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 690 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 690 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 690 times.
✗ Branch 11 not taken.
690 old_catalog->InsertNestedCatalog(new_catalog->mountpoint().ToString(), NULL,
855 690 shash::Any(spooler_->GetHashAlgorithm()), 0);
856
857 // Fix subtree counters in new nested catalogs: subtree is the sum of all
858 // entries of all "grand-nested" catalogs
859 // Note: taking a copy of the nested catalog list here
860 const Catalog::NestedCatalogList
861
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 &grand_nested = wr_new_catalog->ListOwnNestedCatalogs();
862
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 DeltaCounters fix_subtree_counters;
863 1380 for (Catalog::NestedCatalogList::const_iterator i = grand_nested.begin(),
864 690 iEnd = grand_nested.end();
865
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 690 times.
690 i != iEnd;
866 ✗ ++i) {
867 WritableCatalog *grand_catalog;
868 ✗ retval = FindCatalog(i->mountpoint.ToString(), &grand_catalog);
869 ✗ assert(retval);
870 ✗ const Counters &grand_counters = grand_catalog->GetCounters();
871 ✗ grand_counters.AddAsSubtree(&fix_subtree_counters);
872 }
873 690 const DeltaCounters save_counters = wr_new_catalog->delta_counters_;
874 690 wr_new_catalog->delta_counters_ = fix_subtree_counters;
875
1/2
✓ Branch 1 taken 690 times.
✗ Branch 2 not taken.
690 wr_new_catalog->UpdateCounters();
876 690 wr_new_catalog->delta_counters_ = save_counters;
877
878 690 SyncUnlock();
879 690 }
880
881
882 /**
883 * Remove a nested catalog
884 *
885 * If the merged parameter is true, when you remove a nested catalog
886 * all entries currently held by it will be merged into its parent
887 * catalog.
888 * @param mountpoint - the path of the nested catalog to be removed
889 * @param merge - merge the subtree associated with the nested catalog
890 * into its parent catalog
891 * @return - true on success, false otherwise
892 */
893 32 void WritableCatalogManager::RemoveNestedCatalog(const string &mountpoint,
894 const bool merge) {
895
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 const string nested_root_path = MakeRelativePath(mountpoint);
896
897 32 SyncLock();
898 // Find the catalog which should be removed
899 32 WritableCatalog *nested_catalog = NULL;
900
2/4
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
32 if (!FindCatalog(nested_root_path, &nested_catalog)) {
901 ✗ PANIC(kLogStderr,
902 "failed to remove nested catalog '%s': "
903 "mountpoint was not found in current catalog structure",
904 nested_root_path.c_str());
905 }
906
907 // Check if the found catalog is really the nested catalog to be deleted
908
6/19
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 32 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 32 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 32 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 32 times.
✗ Branch 17 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
64 assert(!nested_catalog->IsRoot()
909 && (nested_catalog->mountpoint().ToString() == nested_root_path));
910
911
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 if (merge) {
912 // Merge all data from the nested catalog into it's parent
913
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 nested_catalog->MergeIntoParent();
914 } else {
915 ✗ nested_catalog->RemoveFromParent();
916 }
917
918 // Delete the catalog database file from the working copy
919
2/6
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 32 times.
32 if (unlink(nested_catalog->database_path().c_str()) != 0) {
920 ✗ PANIC(kLogStderr,
921 "unable to delete the removed nested catalog database file '%s'",
922 nested_catalog->database_path().c_str());
923 }
924
925 // Remove the catalog from internal data structures.
926 // On merge the children have been re-parented into this catalog's parent by
927 // MergeIntoParent (CopyCatalogsToParent), so only this catalog is detached.
928 // On removal (fast delete) the whole subtree is discarded together with the
929 // catalog, so any attached children must be detached as well - otherwise they
930 // would be left in the manager with a dangling parent pointer.
931
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 if (merge) {
932
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 DetachCatalog(nested_catalog);
933 } else {
934 ✗ DetachSubtree(nested_catalog);
935 }
936 32 SyncUnlock();
937 32 }
938
939
940 /**
941 * Swap in a new nested catalog
942 *
943 * The old nested catalog must not have been already attached to the
944 * catalog tree. This method will not attach the new nested catalog
945 * to the catalog tree.
946 *
947 * @param mountpoint - the path of the nested catalog to be removed
948 * @param new_hash - the hash of the new nested catalog
949 * @param new_size - the size of the new nested catalog
950 */
951 88 void WritableCatalogManager::SwapNestedCatalog(const string &mountpoint,
952 const shash::Any &new_hash,
953 const uint64_t new_size) {
954
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 const string nested_root_path = MakeRelativePath(mountpoint);
955
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 const string parent_path = GetParentPath(nested_root_path);
956
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 const PathString nested_root_ps = PathString(nested_root_path);
957
958 88 SyncLock();
959
960 // Find the immediate parent catalog
961 88 WritableCatalog *parent = NULL;
962
3/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 75 times.
88 if (!FindCatalog(parent_path, &parent)) {
963 13 SyncUnlock(); // this is needed for the unittest. otherwise they get stuck
964 13 PANIC(kLogStderr,
965 "failed to swap nested catalog '%s': could not find parent '%s'",
966 nested_root_path.c_str(), parent_path.c_str());
967 }
968
969 // Get old nested catalog counters
970
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 Catalog *old_attached_catalog = parent->FindChild(nested_root_ps);
971
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 Counters old_counters;
972
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 62 times.
75 if (old_attached_catalog) {
973 // Old catalog was already attached (e.g. as a child catalog
974 // attached by a prior call to CreateNestedCatalog()). Ensure
975 // that it has not been modified, get counters, and detach it.
976 13 WritableCatalogList list;
977
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
13 if (GetModifiedCatalogLeafsRecursively(old_attached_catalog, &list)) {
978 13 SyncUnlock();
979 13 PANIC(kLogStderr,
980 "failed to swap nested catalog '%s': already modified",
981 nested_root_path.c_str());
982 }
983 ✗ old_counters = old_attached_catalog->GetCounters();
984 ✗ DetachSubtree(old_attached_catalog);
985
986 13 } else {
987 // Old catalog was not attached. Download a freely attached
988 // version and get counters.
989
1/2
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
62 shash::Any old_hash;
990 uint64_t old_size;
991
1/2
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
62 const bool old_found = parent->FindNested(nested_root_ps, &old_hash,
992 &old_size);
993
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 49 times.
62 if (!old_found) {
994 13 SyncUnlock();
995 13 PANIC(kLogStderr,
996 "failed to swap nested catalog '%s': not found in parent",
997 nested_root_path.c_str());
998 }
999 const std::unique_ptr<Catalog> old_free_catalog(
1000
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 LoadFreeCatalog(nested_root_ps, old_hash));
1001
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
49 if (old_free_catalog.get() == nullptr) {
1002 ✗ SyncUnlock();
1003 ✗ PANIC(kLogStderr,
1004 "failed to swap nested catalog '%s': failed to load old catalog",
1005 nested_root_path.c_str());
1006 }
1007 49 old_counters = old_free_catalog->GetCounters();
1008 49 }
1009
1010 // Load freely attached new catalog
1011 const std::unique_ptr<Catalog> new_catalog(
1012
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 13 times.
49 LoadFreeCatalog(nested_root_ps, new_hash));
1013
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
36 if (new_catalog.get() == nullptr) {
1014 ✗ SyncUnlock();
1015 ✗ PANIC(kLogStderr,
1016 "failed to swap nested catalog '%s': failed to load new catalog",
1017 nested_root_path.c_str());
1018 }
1019
1020 // Get new catalog root directory entry
1021
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 DirectoryEntry dirent;
1022 36 XattrList xattrs;
1023
1/2
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 const bool dirent_found = new_catalog->LookupPath(nested_root_ps, &dirent);
1024
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if (!dirent_found) {
1025 ✗ SyncUnlock();
1026 ✗ PANIC(kLogStderr,
1027 "failed to swap nested catalog '%s': missing dirent in new catalog",
1028 nested_root_path.c_str());
1029 }
1030
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
36 if (dirent.HasXattrs()) {
1031 ✗ const bool xattrs_found = new_catalog->LookupXattrsPath(nested_root_ps,
1032 &xattrs);
1033 ✗ if (!xattrs_found) {
1034 ✗ SyncUnlock();
1035 ✗ PANIC(kLogStderr,
1036 "failed to swap nested catalog '%s': missing xattrs in new catalog",
1037 nested_root_path.c_str());
1038 }
1039 }
1040
1041 // Swap catalogs
1042
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 parent->RemoveNestedCatalog(nested_root_path, NULL);
1043
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 parent->InsertNestedCatalog(nested_root_path, NULL, new_hash, new_size);
1044
1045 // Update parent directory entry
1046 36 dirent.set_is_nested_catalog_mountpoint(true);
1047 36 dirent.set_is_nested_catalog_root(false);
1048
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 parent->UpdateEntry(dirent, nested_root_path);
1049
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 parent->TouchEntry(dirent, xattrs, nested_root_path);
1050
1051 // Update counters
1052
1/2
✓ Branch 3 taken 36 times.
✗ Branch 4 not taken.
36 const DeltaCounters delta = Counters::Diff(old_counters,
1053 new_catalog->GetCounters());
1054
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 delta.PopulateToParent(&parent->delta_counters_);
1055
1056 36 SyncUnlock();
1057 192 }
1058
1059 /**
1060 * Install a nested catalog (catalog hierarchy) at a new, empty directory
1061 *
1062 * The mountpoint directory must not yet exist. Its parent directory, however
1063 * must exist. This method combines functionality from AddDirectory(),
1064 * CreateNestedCatalog() and SwapNestedCatalog().
1065 * The new nested catalog won't get attached.
1066 *
1067 * @param mountpoint - the path where the nested catalog should be installed
1068 * @param new_hash - the hash of the new nested catalog
1069 * @param new_size - the size of the new nested catalog
1070 */
1071 39 void WritableCatalogManager::GraftNestedCatalog(const string &mountpoint,
1072 const shash::Any &new_hash,
1073 const uint64_t new_size) {
1074
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 13 times.
39 if (!TryGraftNestedCatalog(mountpoint, new_hash, new_size)) {
1075 26 PANIC(kLogStderr, "failed to graft nested catalog '%s'",
1076 mountpoint.c_str());
1077 }
1078 13 }
1079
1080 39 bool WritableCatalogManager::TryGraftNestedCatalog(const string &mountpoint,
1081 const shash::Any &new_hash,
1082 const uint64_t new_size) {
1083
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 const string nested_root_path = MakeRelativePath(mountpoint);
1084
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 const string parent_path = GetParentPath(nested_root_path);
1085
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 const PathString nested_root_ps = PathString(nested_root_path);
1086
1087
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
39 if (nested_root_path.empty()) {
1088 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1089 "failed to graft nested catalog: empty mountpoint");
1090 ✗ return false;
1091 }
1092
1093 // Load freely attached new catalog
1094 const std::unique_ptr<Catalog> new_catalog(
1095
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 LoadFreeCatalog(nested_root_ps, new_hash));
1096
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
39 if (new_catalog.get() == nullptr) {
1097 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1098 "failed to graft nested catalog '%s': failed to load new catalog",
1099 nested_root_path.c_str());
1100 ✗ return false;
1101 }
1102
4/7
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 39 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 13 times.
✓ Branch 9 taken 26 times.
39 if (new_catalog->root_prefix() != nested_root_ps) {
1103
1/7
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
13 LogCvmfs(kLogCatalog, kLogStderr,
1104 "invalid nested catalog for grafting at '%s': catalog rooted at "
1105 "'%s'",
1106 nested_root_path.c_str(),
1107
2/4
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
26 new_catalog->root_prefix().ToString().c_str());
1108 13 return false;
1109 }
1110
1111 // Get new catalog root directory entry
1112
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 DirectoryEntry dirent;
1113 26 XattrList xattrs;
1114
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 const bool dirent_found = new_catalog->LookupPath(nested_root_ps, &dirent);
1115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (!dirent_found) {
1116 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1117 "failed to graft nested catalog '%s': missing dirent in new "
1118 "catalog",
1119 nested_root_path.c_str());
1120 ✗ return false;
1121 }
1122
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 if (!dirent.IsDirectory()) {
1123 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1124 "failed to graft nested catalog '%s': root entry is not a "
1125 "directory",
1126 nested_root_path.c_str());
1127 ✗ return false;
1128 }
1129
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 if (dirent.HasXattrs()) {
1130 ✗ const bool xattrs_found = new_catalog->LookupXattrsPath(nested_root_ps,
1131 &xattrs);
1132 ✗ if (!xattrs_found) {
1133 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1134 "failed to graft nested catalog '%s': missing xattrs in new "
1135 "catalog",
1136 nested_root_path.c_str());
1137 ✗ return false;
1138 }
1139 }
1140 // Transform the nested catalog root into a transition point to be inserted
1141 // in the parent catalog
1142 26 dirent.set_is_nested_catalog_root(false);
1143 26 dirent.set_is_nested_catalog_mountpoint(true);
1144
1145 // Add directory and nested catalog
1146
1147 26 SyncLock();
1148 WritableCatalog *parent_catalog;
1149
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 DirectoryEntry parent_entry;
1150
2/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
26 if (!FindCatalog(parent_path, &parent_catalog, &parent_entry)) {
1151 ✗ SyncUnlock();
1152 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1153 "catalog for directory '%s' cannot be found", parent_path.c_str());
1154 ✗ return false;
1155 }
1156
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 if (!parent_entry.IsDirectory()) {
1157 ✗ SyncUnlock();
1158 ✗ LogCvmfs(kLogCatalog, kLogStderr, "parent path '%s' is not a directory",
1159 parent_path.c_str());
1160 ✗ return false;
1161 }
1162
3/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 13 times.
26 if (parent_catalog->LookupPath(nested_root_ps, NULL)) {
1163 13 SyncUnlock();
1164
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 LogCvmfs(kLogCatalog, kLogStderr,
1165 "invalid attempt to graft nested catalog into existing directory "
1166 "'%s'",
1167 nested_root_path.c_str());
1168 13 return false;
1169 }
1170
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 parent_catalog->AddEntry(dirent, xattrs, nested_root_path, parent_path);
1171 13 parent_entry.set_linkcount(parent_entry.linkcount() + 1);
1172
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 parent_catalog->UpdateEntry(parent_entry, parent_path);
1173
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 if (parent_entry.IsNestedCatalogRoot()) {
1174 WritableCatalog *grand_parent_catalog = reinterpret_cast<WritableCatalog *>(
1175 13 parent_catalog->parent());
1176 13 parent_entry.set_is_nested_catalog_root(false);
1177 13 parent_entry.set_is_nested_catalog_mountpoint(true);
1178
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 grand_parent_catalog->UpdateEntry(parent_entry, parent_path);
1179 }
1180
1181
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 parent_catalog->InsertNestedCatalog(nested_root_path, NULL, new_hash,
1182 new_size);
1183
1184 // Fix-up counters
1185
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 const Counters counters;
1186
1/2
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
13 const DeltaCounters delta = Counters::Diff(counters,
1187 new_catalog->GetCounters());
1188
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 delta.PopulateToParent(&parent_catalog->delta_counters_);
1189
1190 13 SyncUnlock();
1191 13 return true;
1192 39 }
1193
1194 /**
1195 * Checks if a nested catalog starts at this path. The path must be valid.
1196 */
1197 ✗ bool WritableCatalogManager::IsTransitionPoint(const string &mountpoint) {
1198 ✗ const string path = MakeRelativePath(mountpoint);
1199
1200 ✗ SyncLock();
1201 WritableCatalog *catalog;
1202 ✗ DirectoryEntry entry;
1203 ✗ if (!FindCatalog(path, &catalog, &entry)) {
1204 ✗ PANIC(kLogStderr, "catalog for directory '%s' cannot be found",
1205 path.c_str());
1206 }
1207 ✗ const bool result = entry.IsNestedCatalogRoot();
1208 ✗ SyncUnlock();
1209 ✗ return result;
1210 }
1211
1212
1213 ✗ void WritableCatalogManager::PrecalculateListings() {
1214 // TODO(jblomer): meant for micro catalogs
1215 }
1216
1217
1218 ✗ void WritableCatalogManager::SetTTL(const uint64_t new_ttl) {
1219 ✗ SyncLock();
1220 ✗ reinterpret_cast<WritableCatalog *>(GetRootCatalog())->SetTTL(new_ttl);
1221 ✗ SyncUnlock();
1222 }
1223
1224
1225 ✗ bool WritableCatalogManager::SetVOMSAuthz(const std::string &voms_authz) {
1226 bool result;
1227 ✗ SyncLock();
1228 ✗ result = reinterpret_cast<WritableCatalog *>(GetRootCatalog())
1229 ✗ ->SetVOMSAuthz(voms_authz);
1230 ✗ SyncUnlock();
1231 ✗ return result;
1232 }
1233
1234
1235 540 bool WritableCatalogManager::Commit(const bool stop_for_tweaks,
1236 const uint64_t manual_revision,
1237 manifest::Manifest *manifest) {
1238 WritableCatalog *root_catalog = reinterpret_cast<WritableCatalog *>(
1239 540 GetRootCatalog());
1240
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 root_catalog->SetDirty();
1241
1242 // set root catalog revision to manually provided number if available
1243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 540 times.
540 if (manual_revision > 0) {
1244 ✗ const uint64_t revision = root_catalog->GetRevision();
1245 ✗ if (revision >= manual_revision) {
1246 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1247 "Manual revision (%" PRIu64 ") must not be "
1248 "smaller than the current root catalog's (%" PRIu64
1249 "). Skipped!",
1250 manual_revision, revision);
1251 } else {
1252 // Gets incremented by FinalizeCatalog() afterwards!
1253 ✗ root_catalog->SetRevision(manual_revision - 1);
1254 }
1255 }
1256
1257 // do the actual catalog snapshotting and upload
1258
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 CatalogInfo root_catalog_info;
1259
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 if (getenv("_CVMFS_SERIALIZED_CATALOG_PROCESSING_") == NULL)
1260
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 root_catalog_info = SnapshotCatalogs(stop_for_tweaks);
1261 else
1262 ✗ root_catalog_info = SnapshotCatalogsSerialized(stop_for_tweaks);
1263
2/4
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 540 times.
540 if (spooler_->GetNumberOfErrors() > 0) {
1264 ✗ LogCvmfs(kLogCatalog, kLogStderr, "failed to commit catalogs");
1265 ✗ return false;
1266 }
1267
1268 // .cvmfspublished export
1269
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 LogCvmfs(kLogCatalog, kLogVerboseMsg, "Committing repository manifest");
1270 540 set_base_hash(root_catalog_info.content_hash);
1271
1272 540 manifest->set_catalog_hash(root_catalog_info.content_hash);
1273 540 manifest->set_catalog_size(root_catalog_info.size);
1274
2/4
✓ Branch 2 taken 540 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 540 times.
✗ Branch 6 not taken.
540 manifest->set_root_path("");
1275 540 manifest->set_ttl(root_catalog_info.ttl);
1276 540 manifest->set_revision(root_catalog_info.revision);
1277
1278 540 return true;
1279 }
1280
1281
1282 /**
1283 * Handles the snapshotting of dirty (i.e. modified) catalogs while trying to
1284 * parallelize the compression and upload as much as possible. We use a parallel
1285 * depth first post order tree traversal based on 'continuations'.
1286 *
1287 * The idea is as follows:
1288 * 1. find all leaf-catalogs (i.e. dirty catalogs with no dirty children)
1289 * --> these can be processed and uploaded immediately and independently
1290 * see WritableCatalogManager::GetModifiedCatalogLeafs()
1291 * 2. annotate non-leaf catalogs with their number of dirty children
1292 * --> a finished child will notify it's parent and decrement this number
1293 * see WritableCatalogManager::CatalogUploadCallback()
1294 * 3. if a non-leaf catalog's dirty children number reaches 0, it is scheduled
1295 * for processing as well (continuation)
1296 * --> the parallel processing walks bottom-up through the catalog tree
1297 * see WritableCatalogManager::CatalogUploadCallback()
1298 * 4. when the root catalog is reached, we notify the main thread and return
1299 * --> done through a Future<> in WritableCatalogManager::SnapshotCatalogs
1300 *
1301 * Note: The catalog finalisation (see WritableCatalogManager::FinalizeCatalog)
1302 * happens in a worker thread (i.e. the callback method) for non-leaf
1303 * catalogs.
1304 *
1305 * TODO(rmeusel): since all leaf catalogs are finalized in the main thread, we
1306 * sacrifice some potential concurrency for simplicity.
1307 */
1308 540 WritableCatalogManager::CatalogInfo WritableCatalogManager::SnapshotCatalogs(
1309 const bool stop_for_tweaks) {
1310 // prepare environment for parallel processing
1311
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 Future<CatalogInfo> root_catalog_info_future;
1312 CatalogUploadContext upload_context;
1313 540 upload_context.root_catalog_info = &root_catalog_info_future;
1314 540 upload_context.stop_for_tweaks = stop_for_tweaks;
1315
1316
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 spooler_->RegisterListener(&WritableCatalogManager::CatalogUploadCallback,
1317 this, upload_context);
1318
1319 // find dirty leaf catalogs and annotate non-leaf catalogs (dirty child count)
1320 // post-condition: the entire catalog tree is ready for concurrent processing
1321 540 WritableCatalogList leafs_to_snapshot;
1322
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 GetModifiedCatalogLeafs(&leafs_to_snapshot);
1323
1324 // finalize and schedule the catalog processing
1325 540 WritableCatalogList::const_iterator i = leafs_to_snapshot.begin();
1326 540 const WritableCatalogList::const_iterator iend = leafs_to_snapshot.end();
1327
2/2
✓ Branch 2 taken 752 times.
✓ Branch 3 taken 540 times.
1292 for (; i != iend; ++i) {
1328
1/2
✓ Branch 2 taken 752 times.
✗ Branch 3 not taken.
752 FinalizeCatalog(*i, stop_for_tweaks);
1329
1/2
✓ Branch 2 taken 752 times.
✗ Branch 3 not taken.
752 ScheduleCatalogProcessing(*i);
1330 }
1331
1332
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 LogCvmfs(kLogCatalog, kLogVerboseMsg, "waiting for upload of catalogs");
1333
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 const CatalogInfo &root_catalog_info = root_catalog_info_future.Get();
1334
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 spooler_->WaitForUpload();
1335
1336
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 spooler_->UnregisterListeners();
1337 540 return root_catalog_info;
1338 540 }
1339
1340
1341 1191 void WritableCatalogManager::FinalizeCatalog(WritableCatalog *catalog,
1342 const bool stop_for_tweaks) {
1343 // update meta information of this catalog
1344
1/3
✓ Branch 2 taken 1191 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1191 LogCvmfs(kLogCatalog, kLogVerboseMsg, "creating snapshot of catalog '%s'",
1345 2382 catalog->mountpoint().c_str());
1346
1347 1191 catalog->UpdateCounters();
1348 1191 catalog->UpdateLastModified();
1349 1191 catalog->IncrementRevision();
1350
1351 // update the previous catalog revision pointer
1352
2/2
✓ Branch 1 taken 540 times.
✓ Branch 2 taken 651 times.
1191 if (catalog->IsRoot()) {
1353
1/4
✓ Branch 2 taken 540 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
540 LogCvmfs(kLogCatalog, kLogVerboseMsg,
1354 "setting '%s' as previous revision "
1355 "for root catalog",
1356 1080 base_hash().ToStringWithSuffix().c_str());
1357 540 catalog->SetPreviousRevision(base_hash());
1358 } else {
1359 // Multiple catalogs might query the parent concurrently
1360 651 SyncLock();
1361
1/2
✓ Branch 1 taken 651 times.
✗ Branch 2 not taken.
651 shash::Any hash_previous;
1362 uint64_t size_previous;
1363
1/2
✓ Branch 2 taken 651 times.
✗ Branch 3 not taken.
1302 const bool retval = catalog->parent()->FindNested(
1364
1/2
✓ Branch 1 taken 651 times.
✗ Branch 2 not taken.
1302 catalog->mountpoint(), &hash_previous, &size_previous);
1365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 651 times.
651 assert(retval);
1366 651 SyncUnlock();
1367
1368
1/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 651 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
1302 LogCvmfs(kLogCatalog, kLogVerboseMsg,
1369 "found '%s' as previous revision "
1370 "for nested catalog '%s'",
1371
1/2
✓ Branch 1 taken 651 times.
✗ Branch 2 not taken.
1302 hash_previous.ToStringWithSuffix().c_str(),
1372
1/2
✓ Branch 1 taken 651 times.
✗ Branch 2 not taken.
1302 catalog->mountpoint().c_str());
1373
1/2
✓ Branch 1 taken 651 times.
✗ Branch 2 not taken.
651 catalog->SetPreviousRevision(hash_previous);
1374 }
1375 1191 catalog->Commit();
1376
1377 // check if catalog has too many entries
1378 const uint64_t catalog_limit = uint64_t(1000)
1379
2/2
✓ Branch 1 taken 540 times.
✓ Branch 2 taken 651 times.
1191 * uint64_t((catalog->IsRoot()
1380 540 ? root_kcatalog_limit_
1381 651 : nested_kcatalog_limit_));
1382 1191 if ((catalog_limit > 0)
1383
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1191 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1191 times.
1191 && (catalog->GetCounters().GetSelfEntries() > catalog_limit)) {
1384 ✗ LogCvmfs(kLogCatalog, kLogStderr,
1385 "%s: catalog at %s has more than %lu entries (%lu). "
1386 "Large catalogs stress the CernVM-FS transport infrastructure. "
1387 "Please split it into nested catalogs or increase the limit.",
1388 ✗ enforce_limits_ ? "FATAL" : "WARNING",
1389 ✗ (catalog->IsRoot() ? "/" : catalog->mountpoint().c_str()),
1390 ✗ catalog_limit, catalog->GetCounters().GetSelfEntries());
1391 ✗ if (enforce_limits_)
1392 ✗ PANIC(kLogStderr, "catalog at %s has more than %u entries (%u). ",
1393 (catalog->IsRoot() ? "/" : catalog->mountpoint().c_str()),
1394 catalog_limit, catalog->GetCounters().GetSelfEntries());
1395 }
1396
1397 // allow for manual adjustments in the catalog
1398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1191 times.
1191 if (stop_for_tweaks) {
1399 ✗ LogCvmfs(kLogCatalog, kLogStdout,
1400 "Allowing for tweaks in %s at %s "
1401 "(hit return to continue)",
1402 ✗ catalog->database_path().c_str(), catalog->mountpoint().c_str());
1403 ✗ const int read_char = getchar();
1404 ✗ assert(read_char != EOF);
1405 }
1406
1407 // compaction of bloated catalogs (usually after high database churn)
1408 1191 catalog->VacuumDatabaseIfNecessary();
1409 1191 }
1410
1411
1412 1191 void WritableCatalogManager::ScheduleCatalogProcessing(
1413 WritableCatalog *catalog) {
1414 {
1415 1191 const MutexLockGuard guard(catalog_processing_lock_);
1416 // register catalog object for WritableCatalogManager::CatalogUploadCallback
1417
2/4
✓ Branch 1 taken 1191 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1191 times.
✗ Branch 5 not taken.
1191 catalog_processing_map_[catalog->database_path()] = catalog;
1418 1191 }
1419
1/2
✓ Branch 2 taken 1191 times.
✗ Branch 3 not taken.
1191 spooler_->ProcessCatalog(catalog->database_path());
1420 1191 }
1421
1422 /**
1423 * Copy catalog to local cache.server
1424 * Must be an atomic write into the cache_dir
1425 * As such: create a temporary copy in cache_dir/txn and then do a
1426 * `rename` (which is atomic) to the actual cache path
1427 *
1428 * @returns true on success, otherwise false
1429 */
1430 ✗ bool WritableCatalogManager::CopyCatalogToLocalCache(
1431 const upload::SpoolerResult &result) {
1432 ✗ std::string tmp_catalog_path;
1433 ✗ const std::string cache_catalog_path = dir_cache_ + "/"
1434 + result.content_hash
1435 ✗ .MakePathWithoutSuffix();
1436 ✗ FILE *fcatalog = CreateTempFile(dir_cache_ + "/txn/catalog", 0666, "w",
1437 &tmp_catalog_path);
1438 ✗ if (!fcatalog) {
1439 ✗ PANIC(kLogDebug | kLogStderr,
1440 "Creating file for temporary catalog failed: %s",
1441 tmp_catalog_path.c_str());
1442 }
1443 ✗ CopyPath2File(result.local_path.c_str(), fcatalog);
1444 ✗ (void)fclose(fcatalog);
1445
1446 ✗ if (rename(tmp_catalog_path.c_str(), cache_catalog_path.c_str()) != 0) {
1447 ✗ PANIC(kLogDebug | kLogStderr, "Failed to copy catalog from %s to cache %s",
1448 result.local_path.c_str(), cache_catalog_path.c_str());
1449 }
1450 ✗ return true;
1451 }
1452
1453 1191 void WritableCatalogManager::CatalogUploadCallback(
1454 const upload::SpoolerResult &result,
1455 const CatalogUploadContext catalog_upload_context) {
1456
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1191 times.
1191 if (result.return_code != 0) {
1457 ✗ PANIC(kLogStderr, "failed to upload '%s' (retval: %d)",
1458 result.local_path.c_str(), result.return_code);
1459 }
1460
1461 // retrieve the catalog object based on the callback information
1462 // see WritableCatalogManager::ScheduleCatalogProcessing()
1463 1191 WritableCatalog *catalog = NULL;
1464 {
1465 1191 const MutexLockGuard guard(catalog_processing_lock_);
1466 const std::map<std::string, WritableCatalog *>::iterator
1467
1/2
✓ Branch 1 taken 1191 times.
✗ Branch 2 not taken.
1191 c = catalog_processing_map_.find(result.local_path);
1468
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 1191 times.
1191 assert(c != catalog_processing_map_.end());
1469 1191 catalog = c->second;
1470 1191 }
1471
1472 1191 const uint64_t catalog_size = GetFileSize(result.local_path);
1473
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1191 times.
1191 assert(catalog_size > 0);
1474
1475
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1191 times.
1191 if (UseLocalCache()) {
1476 ✗ CopyCatalogToLocalCache(result);
1477 }
1478
1479 1191 SyncLock();
1480
2/2
✓ Branch 1 taken 651 times.
✓ Branch 2 taken 540 times.
1191 if (catalog->HasParent()) {
1481 // finalized nested catalogs will update their parent's pointer and schedule
1482 // them for processing (continuation) if the 'dirty children count' == 0
1483 651 LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating nested catalog link");
1484 651 WritableCatalog *parent = catalog->GetWritableParent();
1485
1486
2/4
✓ Branch 1 taken 651 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 651 times.
✗ Branch 5 not taken.
651 parent->UpdateNestedCatalog(catalog->mountpoint().ToString(),
1487 651 result.content_hash,
1488 catalog_size,
1489 651 catalog->delta_counters_);
1490 651 catalog->delta_counters_.SetZero();
1491
1492 const int remaining_dirty_children = catalog->GetWritableParent()
1493 651 ->DecrementDirtyChildren();
1494
1495 651 SyncUnlock();
1496
1497 // continuation of the dirty catalog tree traversal
1498 // see WritableCatalogManager::SnapshotCatalogs()
1499
2/2
✓ Branch 0 taken 439 times.
✓ Branch 1 taken 212 times.
651 if (remaining_dirty_children == 0) {
1500 439 FinalizeCatalog(parent, catalog_upload_context.stop_for_tweaks);
1501 439 ScheduleCatalogProcessing(parent);
1502 }
1503
1504
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 } else if (catalog->IsRoot()) {
1505 // once the root catalog is reached, we are done with processing and report
1506 // back to the main via a Future<> and provide the necessary information
1507
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 CatalogInfo root_catalog_info;
1508 540 root_catalog_info.size = catalog_size;
1509
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 root_catalog_info.ttl = catalog->GetTTL();
1510 540 root_catalog_info.content_hash = result.content_hash;
1511
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 root_catalog_info.revision = catalog->GetRevision();
1512
1/2
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
540 catalog_upload_context.root_catalog_info->Set(root_catalog_info);
1513 540 SyncUnlock();
1514 } else {
1515 ✗ PANIC(kLogStderr, "inconsistent state detected");
1516 }
1517 1191 }
1518
1519
1520 /**
1521 * Finds dirty catalogs that can be snapshot right away and annotates all the
1522 * other catalogs with their number of dirty descendants.
1523 * Note that there is a convenience wrapper to start the recursion:
1524 * WritableCatalogManager::GetModifiedCatalogLeafs()
1525 *
1526 * @param catalog the catalog for this recursion step
1527 * @param result the result list to be appended to
1528 * @return true if 'catalog' is dirty
1529 */
1530 1204 bool WritableCatalogManager::GetModifiedCatalogLeafsRecursively(
1531 Catalog *catalog, WritableCatalogList *result) const {
1532 1204 WritableCatalog *wr_catalog = static_cast<WritableCatalog *>(catalog);
1533
1534 // Look for dirty catalogs in the descendants of *catalog
1535 1204 int dirty_children = 0;
1536
1/2
✓ Branch 1 taken 1204 times.
✗ Branch 2 not taken.
1204 CatalogList children = wr_catalog->GetChildren();
1537 1204 CatalogList::const_iterator i = children.begin();
1538 1204 const CatalogList::const_iterator iend = children.end();
1539
2/2
✓ Branch 2 taken 651 times.
✓ Branch 3 taken 1204 times.
1855 for (; i != iend; ++i) {
1540
2/4
✓ Branch 2 taken 651 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 651 times.
✗ Branch 5 not taken.
651 if (GetModifiedCatalogLeafsRecursively(*i, result)) {
1541 651 ++dirty_children;
1542 }
1543 }
1544
1545 // a catalog is dirty if itself or one of its children has changed
1546 // a leaf catalog doesn't have any dirty children
1547 1204 wr_catalog->set_dirty_children(dirty_children);
1548
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1204 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1204 const bool is_dirty = wr_catalog->IsDirty() || dirty_children > 0;
1549 1204 const bool is_leaf = dirty_children == 0;
1550
3/4
✓ Branch 0 taken 1204 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 765 times.
✓ Branch 3 taken 439 times.
1204 if (is_dirty && is_leaf) {
1551
1/2
✓ Branch 1 taken 765 times.
✗ Branch 2 not taken.
765 result->push_back(const_cast<WritableCatalog *>(wr_catalog));
1552 }
1553
1554 1204 return is_dirty;
1555 1204 }
1556
1557
1558 ✗ void WritableCatalogManager::DoBalance() {
1559 ✗ CatalogList catalog_list = GetCatalogs();
1560 ✗ reverse(catalog_list.begin(), catalog_list.end());
1561 ✗ for (unsigned i = 0; i < catalog_list.size(); ++i) {
1562 ✗ FixWeight(static_cast<WritableCatalog *>(catalog_list[i]));
1563 }
1564 }
1565
1566 ✗ void WritableCatalogManager::FixWeight(WritableCatalog *catalog) {
1567 // firstly check underflow because they can provoke overflows
1568 ✗ if (catalog->GetNumEntries() < min_weight_ && !catalog->IsRoot()
1569 ✗ && catalog->IsAutogenerated()) {
1570 ✗ LogCvmfs(kLogCatalog, kLogStdout,
1571 "Deleting an autogenerated catalog in '%s'",
1572 ✗ catalog->mountpoint().c_str());
1573 // Remove the .cvmfscatalog and .cvmfsautocatalog files first
1574 ✗ const string path = catalog->mountpoint().ToString();
1575 ✗ catalog->RemoveEntry(path + "/.cvmfscatalog");
1576 ✗ catalog->RemoveEntry(path + "/.cvmfsautocatalog");
1577 // Remove the actual catalog
1578 ✗ const string catalog_path = catalog->mountpoint().ToString().substr(1);
1579 ✗ RemoveNestedCatalog(catalog_path);
1580 ✗ } else if (catalog->GetNumEntries() > max_weight_) {
1581 ✗ CatalogBalancer<WritableCatalogManager> catalog_balancer(this);
1582 ✗ catalog_balancer.Balance(catalog);
1583 }
1584 }
1585
1586
1587 //****************************************************************************
1588 // Workaround -- Serialized Catalog Committing
1589
1590 ✗ int WritableCatalogManager::GetModifiedCatalogsRecursively(
1591 const Catalog *catalog, WritableCatalogList *result) const {
1592 // A catalog must be snapshot, if itself or one of it's descendants is dirty.
1593 // So we traverse the catalog tree recursively and look for dirty catalogs
1594 // on the way.
1595 ✗ const WritableCatalog *wr_catalog = static_cast<const WritableCatalog *>(
1596 catalog);
1597 // This variable will contain the number of dirty catalogs in the sub tree
1598 // with *catalog as it's root.
1599 ✗ int dirty_catalogs = (wr_catalog->IsDirty()) ? 1 : 0;
1600
1601 // Look for dirty catalogs in the descendants of *catalog
1602 ✗ CatalogList children = wr_catalog->GetChildren();
1603 ✗ for (CatalogList::const_iterator i = children.begin(), iEnd = children.end();
1604 ✗ i != iEnd; ++i) {
1605 ✗ dirty_catalogs += GetModifiedCatalogsRecursively(*i, result);
1606 }
1607
1608 // If we found a dirty catalog in the checked sub tree, the root (*catalog)
1609 // must be snapshot and ends up in the result list
1610 ✗ if (dirty_catalogs > 0)
1611 ✗ result->push_back(const_cast<WritableCatalog *>(wr_catalog));
1612
1613 // tell the upper layer about number of catalogs
1614 ✗ return dirty_catalogs;
1615 }
1616
1617
1618 ✗ void WritableCatalogManager::CatalogUploadSerializedCallback(
1619 const upload::SpoolerResult &result, const CatalogUploadContext unused) {
1620 ✗ if (result.return_code != 0) {
1621 ✗ PANIC(kLogStderr, "failed to upload '%s' (retval: %d)",
1622 result.local_path.c_str(), result.return_code);
1623 }
1624
1625 ✗ if (UseLocalCache()) {
1626 ✗ CopyCatalogToLocalCache(result);
1627 }
1628
1629 ✗ unlink(result.local_path.c_str());
1630 }
1631
1632
1633 WritableCatalogManager::CatalogInfo
1634 ✗ WritableCatalogManager::SnapshotCatalogsSerialized(const bool stop_for_tweaks) {
1635 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "Serialized committing of file catalogs...");
1636 ✗ reinterpret_cast<WritableCatalog *>(GetRootCatalog())->SetDirty();
1637 ✗ WritableCatalogList catalogs_to_snapshot;
1638 ✗ GetModifiedCatalogs(&catalogs_to_snapshot);
1639 CatalogUploadContext unused;
1640 ✗ unused.root_catalog_info = NULL;
1641 ✗ unused.stop_for_tweaks = false;
1642 ✗ spooler_->RegisterListener(
1643 &WritableCatalogManager::CatalogUploadSerializedCallback, this, unused);
1644
1645 ✗ CatalogInfo root_catalog_info;
1646 ✗ WritableCatalogList::const_iterator i = catalogs_to_snapshot.begin();
1647 ✗ const WritableCatalogList::const_iterator iend = catalogs_to_snapshot.end();
1648 ✗ for (; i != iend; ++i) {
1649 ✗ FinalizeCatalog(*i, stop_for_tweaks);
1650
1651 // Compress and upload catalog
1652 ✗ shash::Any hash_catalog(spooler_->GetHashAlgorithm(),
1653 ✗ shash::kSuffixCatalog);
1654 ✗ if (!zlib::CompressPath2Null((*i)->database_path(), &hash_catalog)) {
1655 ✗ PANIC(kLogStderr, "could not compress catalog %s",
1656 (*i)->mountpoint().ToString().c_str());
1657 }
1658
1659 ✗ const int64_t catalog_size = GetFileSize((*i)->database_path());
1660 ✗ assert(catalog_size > 0);
1661
1662 ✗ if ((*i)->HasParent()) {
1663 ✗ LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating nested catalog link");
1664 ✗ WritableCatalog *parent = (*i)->GetWritableParent();
1665 ✗ parent->UpdateNestedCatalog((*i)->mountpoint().ToString(), hash_catalog,
1666 ✗ catalog_size, (*i)->delta_counters_);
1667 ✗ (*i)->delta_counters_.SetZero();
1668 ✗ } else if ((*i)->IsRoot()) {
1669 ✗ root_catalog_info.size = catalog_size;
1670 ✗ root_catalog_info.ttl = (*i)->GetTTL();
1671 ✗ root_catalog_info.content_hash = hash_catalog;
1672 ✗ root_catalog_info.revision = (*i)->GetRevision();
1673 } else {
1674 ✗ PANIC(kLogStderr, "inconsistent state detected");
1675 }
1676
1677 ✗ spooler_->ProcessCatalog((*i)->database_path());
1678 }
1679 ✗ spooler_->WaitForUpload();
1680
1681 ✗ spooler_->UnregisterListeners();
1682 ✗ return root_catalog_info;
1683 }
1684
1685 ✗ void WritableCatalogManager::SetupSingleCatalogUploadCallback() {
1686 ✗ spooler_->RegisterListener(
1687 &WritableCatalogManager::SingleCatalogUploadCallback, this);
1688 }
1689
1690 ✗ void WritableCatalogManager::RemoveSingleCatalogUploadCallback() {
1691 ✗ spooler_->WaitForUpload(); // wait for all outstanding jobs to finish before
1692 // tearing it down
1693 ✗ spooler_->UnregisterListeners();
1694 ✗ pending_catalogs_ =
1695 ✗ {}; // whatever we couldn't process, leave it to the Commit
1696 }
1697
1698 ✗ void WritableCatalogManager::AddCatalogToQueue(const std::string &path) {
1699 ✗ SyncLock();
1700 ✗ WritableCatalog *catalog = NULL;
1701 ✗ bool const retval = FindCatalog(MakeRelativePath(path), &catalog, NULL);
1702 ✗ assert(retval);
1703 ✗ assert(catalog);
1704 ✗ catalog->SetDirty(); // ensure it's dirty so its parent will wait for it
1705 ✗ SyncUnlock();
1706 ✗ pending_catalogs_.push_back(catalog);
1707 }
1708
1709 ✗ void WritableCatalogManager::ScheduleReadyCatalogs() {
1710 // best effort to schedule as many catalogs for upload as possible
1711 ✗ for (auto it = pending_catalogs_.begin(); it != pending_catalogs_.end();) {
1712 ✗ if ((*it)->dirty_children() == 0) {
1713 ✗ FinalizeCatalog(*it, false /* stop_for_tweaks */);
1714 ✗ ScheduleCatalogProcessing(*it);
1715 ✗ LogCvmfs(kLogCatalog, kLogVerboseMsg, "scheduled %s for processing",
1716 ✗ (*it)->mountpoint().c_str());
1717 ✗ it = pending_catalogs_.erase(it);
1718 } else {
1719 ✗ ++it;
1720 }
1721 }
1722 }
1723
1724 // Callback for uploading a single catalog, similar to CatalogUploadCallback.
1725 // The main difference is that this callback would not trigger processing of the
1726 // parent
1727 ✗ void WritableCatalogManager::SingleCatalogUploadCallback(
1728 const upload::SpoolerResult &result) {
1729 ✗ if (result.return_code != 0) {
1730 ✗ PANIC(kLogStderr, "failed to upload '%s' (retval: %d)",
1731 result.local_path.c_str(), result.return_code);
1732 }
1733
1734 // retrieve the catalog object based on the callback information
1735 // see WritableCatalogManager::ScheduleCatalogProcessing()
1736 ✗ WritableCatalog *catalog = NULL;
1737 {
1738 ✗ MutexLockGuard const guard(catalog_processing_lock_);
1739 std::map<std::string, WritableCatalog *>::iterator const
1740 ✗ c = catalog_processing_map_.find(result.local_path);
1741 ✗ assert(c != catalog_processing_map_.end());
1742 ✗ catalog = c->second;
1743 }
1744
1745 ✗ uint64_t const catalog_size = GetFileSize(result.local_path);
1746 ✗ assert(catalog_size > 0);
1747
1748 ✗ SyncLock();
1749 ✗ if (catalog->HasParent()) {
1750 // finalized nested catalogs will update their parent's pointer
1751 ✗ LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating nested catalog link");
1752 ✗ WritableCatalog *parent = catalog->GetWritableParent();
1753
1754 ✗ parent->UpdateNestedCatalog(catalog->mountpoint().ToString(),
1755 ✗ result.content_hash,
1756 catalog_size,
1757 ✗ catalog->delta_counters_);
1758 ✗ parent->DecrementDirtyChildren();
1759 ✗ catalog->delta_counters_.SetZero();
1760 }
1761 // JUMP: detach the catalog after uploading to free sqlite related resources
1762 ✗ DetachCatalog(catalog);
1763 ✗ SyncUnlock();
1764 }
1765 // using the given list of dirs, fetch all relevant catalogs
1766 ✗ void WritableCatalogManager::LoadCatalogs(
1767 const std::string &base_path, const std::unordered_set<std::string> &dirs) {
1768 // mount everything up to "base_path" first (this would be our lease_path
1769 // typically)
1770 Catalog *base_catalog;
1771 ✗ MountSubtree(PathString(base_path), NULL /* entry_point */,
1772 true /* is_listable */, &base_catalog);
1773
1774 // start up the downloader
1775 CatalogDownloadContext context;
1776 ✗ context.dirs = &dirs;
1777 ✗ catalog_download_pipeline_ = new CatalogDownloadPipeline(
1778 ✗ static_cast<SimpleCatalogManager *>(this));
1779 ✗ catalog_download_pipeline_->RegisterListener(
1780 &WritableCatalogManager::CatalogDownloadCallback, this, context);
1781 ✗ catalog_download_pipeline_->Spawn();
1782
1783 Catalog::NestedCatalogList nested_catalogs = base_catalog
1784 ✗ ->ListNestedCatalogs();
1785 ✗ for (auto it = nested_catalogs.begin(); it != nested_catalogs.end(); ++it) {
1786 // schedule relevant child nested catalogs for download
1787 ✗ std::string const mountpoint = it->mountpoint.ToString();
1788 ✗ if (dirs.find(mountpoint) != dirs.end()) {
1789 ✗ Catalog *catalog = CreateCatalog(it->mountpoint, it->hash,
1790 ✗ NULL /* parent */);
1791 {
1792 ✗ MutexLockGuard const guard(catalog_download_lock_);
1793 ✗ catalog_download_map_.insert(
1794 ✗ std::make_pair(it->hash.ToString(), catalog));
1795 }
1796 ✗ catalog_download_pipeline_->Process(it->hash);
1797 }
1798 }
1799
1800 ✗ catalog_download_pipeline_->WaitFor();
1801 ✗ delete catalog_download_pipeline_; // terminate all the threads
1802 }
1803
1804 ✗ bool WritableCatalogManager::LookupDirEntry(const string &path,
1805 const LookupOptions options,
1806 DirectoryEntry *dirent) {
1807 ✗ SyncLock();
1808 ✗ bool const exists = LookupPath(path, options, dirent);
1809 ✗ SyncUnlock();
1810 ✗ return exists;
1811 }
1812
1813 ✗ void WritableCatalogManager::CatalogHashSerializedCallback(
1814 const CompressHashResult &result) {
1815 ✗ MutexLockGuard const guard(catalog_hash_lock_);
1816 ✗ catalog_hash_map_[result.path] = result.hash;
1817 }
1818
1819 ✗ void WritableCatalogManager::CatalogDownloadCallback(
1820 const CatalogDownloadResult &result, CatalogDownloadContext context) {
1821 Catalog *downloaded_catalog;
1822 {
1823 ✗ MutexLockGuard const guard(catalog_download_lock_);
1824 ✗ auto it = catalog_download_map_.find(result.hash);
1825 ✗ assert(it != catalog_download_map_.end());
1826 ✗ downloaded_catalog = it->second;
1827 }
1828
1829 ✗ if (!downloaded_catalog->OpenDatabase(result.db_path)) {
1830 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "failed to initialize catalog");
1831 ✗ delete downloaded_catalog;
1832 ✗ return;
1833 }
1834
1835 Catalog::NestedCatalogList nested_catalogs = downloaded_catalog
1836 ✗ ->ListNestedCatalogs();
1837 ✗ for (auto it = nested_catalogs.begin(); it != nested_catalogs.end(); ++it) {
1838 // schedule relevant child nested catalogs for download
1839 ✗ if (context.dirs->find(it->mountpoint.ToString()) != context.dirs->end()) {
1840 ✗ Catalog *child_catalog = CreateCatalog(it->mountpoint, it->hash,
1841 ✗ NULL /* parent */);
1842 {
1843 ✗ MutexLockGuard const guard(catalog_download_lock_);
1844 ✗ catalog_download_map_.insert(
1845 ✗ std::make_pair(it->hash.ToString(), child_catalog));
1846 }
1847 ✗ catalog_download_pipeline_->Process(it->hash);
1848 }
1849 }
1850 ✗ delete downloaded_catalog;
1851 }
1852
1853
1854 } // namespace catalog
1855