GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/catalog_mgr_rw.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 419 675 62.1%
Branches: 321 1020 31.5%

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