GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/sync_mediator.cc
Date: 2026-08-30 02:40:36
Exec Total Coverage
Lines: 1 635 0.2%
Branches: 0 953 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "sync_mediator.h"
6
7 #include <fcntl.h>
8 #include <inttypes.h>
9 #include <unistd.h>
10
11 #include <cassert>
12 #include <cstdio>
13 #include <cstdlib>
14
15 #include "catalog_virtual.h"
16 #include "compression/compression.h"
17 #include "crypto/hash.h"
18 #include "directory_entry.h"
19 #include "json_document.h"
20 #include "publish/repository.h"
21 #include "sync_union.h"
22 #include "upload.h"
23 #include "util/exception.h"
24 #include "util/fs_traversal.h"
25 #include "util/posix.h"
26 #include "util/string.h"
27
28 using namespace std; // NOLINT
29
30 namespace publish {
31
32 348 AbstractSyncMediator::~AbstractSyncMediator() { }
33
34 SyncMediator::SyncMediator(catalog::WritableCatalogManager *catalog_manager,
35 const SyncParameters *params,
36 perf::StatisticsTemplate statistics)
37 : catalog_manager_(catalog_manager)
38 , union_engine_(NULL)
39 , handle_hardlinks_(false)
40 , recursive_fast_delete_(false)
41 , params_(params)
42 , reporter_(new SyncDiffReporter(params_->print_changeset
43 ? SyncDiffReporter::kPrintChanges
44 : SyncDiffReporter::kPrintDots)) {
45 const int retval = pthread_mutex_init(&lock_file_queue_, NULL);
46 assert(retval == 0);
47
48 params->spooler->RegisterListener(&SyncMediator::PublishFilesCallback, this);
49
50 counters_ = std::unique_ptr<perf::FsCounters>(
51 new perf::FsCounters(statistics));
52 }
53
54 SyncMediator::~SyncMediator() { pthread_mutex_destroy(&lock_file_queue_); }
55
56
57 void SyncMediator::RegisterUnionEngine(SyncUnion *engine) {
58 union_engine_ = engine;
59 handle_hardlinks_ = engine->SupportsHardlinks();
60 }
61
62
63 /**
64 * The entry /.cvmfs or entries in /.cvmfs/ must not be added, removed or
65 * modified manually. The directory /.cvmfs is generated by the VirtualCatalog
66 * class if requested.
67 */
68 void SyncMediator::EnsureAllowed(SharedPtr<SyncItem> entry) {
69 const bool ignore_case_setting = false;
70 const string relative_path = entry->GetRelativePath();
71 if ((relative_path == string(catalog::VirtualCatalog::kVirtualPath))
72 || (HasPrefix(relative_path,
73 string(catalog::VirtualCatalog::kVirtualPath) + "/",
74 ignore_case_setting))) {
75 PANIC(kLogStderr, "[ERROR] invalid attempt to modify %s",
76 relative_path.c_str());
77 }
78 }
79
80
81 /**
82 * Add an entry to the repository.
83 * Added directories will be traversed in order to add the complete subtree.
84 */
85 void SyncMediator::Add(SharedPtr<SyncItem> entry) {
86 EnsureAllowed(entry);
87
88 if (entry->IsDirectory()) {
89 AddDirectoryRecursively(entry);
90 return;
91 }
92
93 if (entry->IsBundleSpec()) {
94 PrintWarning(".cvmfsbundle file encountered. "
95 "Bundles is currently an experimental feature.");
96
97 if (!entry->IsRegularFile() && !entry->IsSymlink()) {
98 PANIC(kLogStderr,
99 "Error: bundle specification must be a regular file or a symlink");
100 }
101 if (entry->HasHardlinks()) {
102 PANIC(kLogStderr, "Error: bundle specification must not be a hard link");
103 }
104
105 InsertBundleSpec(entry);
106 AddFile(entry);
107 return;
108 }
109
110 if (entry->IsRegularFile() || entry->IsSymlink()) {
111 // A file is a hard link if the link count is greater than 1
112 if (entry->HasHardlinks() && handle_hardlinks_)
113 InsertHardlink(entry);
114 else
115 AddFile(entry);
116 return;
117 } else if (entry->IsGraftMarker()) {
118 LogCvmfs(kLogPublish, kLogDebug, "Ignoring graft marker file.");
119 return; // Ignore markers.
120 }
121
122 // In OverlayFS whiteouts can be represented as character devices with major
123 // and minor numbers equal to 0. Special files will be ignored except if they
124 // are whiteout files.
125 if (entry->IsSpecialFile() && !entry->IsWhiteout()) {
126 if (params_->ignore_special_files) {
127 PrintWarning("'" + entry->GetRelativePath()
128 + "' "
129 "is a special file, ignoring.");
130 } else {
131 if (entry->HasHardlinks() && handle_hardlinks_)
132 InsertHardlink(entry);
133 else
134 AddFile(entry);
135 }
136 return;
137 }
138
139 PrintWarning("'" + entry->GetRelativePath()
140 + "' cannot be added. Unrecognized file type.");
141 }
142
143
144 /**
145 * Touch an entry in the repository.
146 */
147 void SyncMediator::Touch(SharedPtr<SyncItem> entry) {
148 EnsureAllowed(entry);
149
150 if (entry->IsGraftMarker()) {
151 return;
152 }
153 if (entry->IsDirectory()) {
154 TouchDirectory(entry);
155 perf::Inc(counters_->n_directories_changed);
156 return;
157 }
158
159 if (entry->IsRegularFile() || entry->IsSymlink() || entry->IsSpecialFile()) {
160 Replace(entry); // This way, hardlink processing is correct
161 // Replace calls Remove; cancel Remove's actions:
162 perf::Xadd(counters_->sz_removed_bytes, -entry->GetRdOnlySize());
163
164 // Count only the difference between the old and new file
165 // Symlinks do not count into added or removed bytes
166 int64_t dif = 0;
167
168 // Need to handle 4 cases (symlink->symlink, symlink->regular,
169 // regular->symlink, regular->regular)
170 if (entry->WasSymlink()) {
171 // Replace calls Remove; cancel Remove's actions:
172 perf::Dec(counters_->n_symlinks_removed);
173
174 if (entry->IsSymlink()) {
175 perf::Inc(counters_->n_symlinks_changed);
176 } else {
177 perf::Inc(counters_->n_symlinks_removed);
178 perf::Inc(counters_->n_files_added);
179 dif += entry->GetScratchSize();
180 }
181 } else {
182 // Replace calls Remove; cancel Remove's actions:
183 perf::Dec(counters_->n_files_removed);
184 dif -= entry->GetRdOnlySize();
185 if (entry->IsSymlink()) {
186 perf::Inc(counters_->n_files_removed);
187 perf::Inc(counters_->n_symlinks_added);
188 } else {
189 perf::Inc(counters_->n_files_changed);
190 dif += entry->GetScratchSize();
191 }
192 }
193
194 if (dif > 0) { // added bytes
195 perf::Xadd(counters_->sz_added_bytes, dif);
196 } else { // removed bytes
197 perf::Xadd(counters_->sz_removed_bytes, -dif);
198 }
199 return;
200 }
201
202 PrintWarning("'" + entry->GetRelativePath()
203 + "' cannot be touched. Unrecognized file type.");
204 }
205
206
207 /**
208 * Remove an entry from the repository. Directories will be recursively removed.
209 */
210 void SyncMediator::Remove(SharedPtr<SyncItem> entry, bool fast_delete) {
211 EnsureAllowed(entry);
212
213 if (entry->WasDirectory()) {
214 RemoveDirectoryRecursively(entry, fast_delete);
215 return;
216 }
217
218 if (entry->WasBundleSpec()) {
219 if (!params_->dry_run)
220 catalog_manager_->UpdateBundleTrigger(GetBundleTriggerPath(entry), false);
221 RemoveFile(entry);
222 return;
223 }
224
225 if (entry->WasRegularFile() || entry->WasSymlink()
226 || entry->WasSpecialFile()) {
227 RemoveFile(entry);
228 return;
229 }
230
231 PrintWarning("'" + entry->GetRelativePath()
232 + "' cannot be deleted. Unrecognized file type.");
233 }
234
235
236 /**
237 * Remove the old entry and add the new one.
238 */
239 void SyncMediator::Replace(SharedPtr<SyncItem> entry) {
240 // EnsureAllowed(entry); <-- Done by Remove() and Add()
241 Remove(entry);
242 Add(entry);
243 }
244
245 bool SyncMediator::Clone(const std::string from, const std::string to,
246 bool fail_if_source_missing) {
247 return catalog_manager_->Clone(from, to, fail_if_source_missing);
248 }
249
250 void SyncMediator::EnterDirectory(SharedPtr<SyncItem> entry) {
251 if (!handle_hardlinks_) {
252 return;
253 }
254
255 const HardlinkGroupMap new_map;
256 hardlink_stack_.push(new_map);
257 }
258
259
260 void SyncMediator::LeaveDirectory(SharedPtr<SyncItem> entry) {
261 if (!handle_hardlinks_) {
262 return;
263 }
264
265 CompleteHardlinks(entry);
266 AddLocalHardlinkGroups(GetHardlinkMap());
267 hardlink_stack_.pop();
268 }
269
270
271 /**
272 * Do any pending processing and commit all changes to the catalogs.
273 * To be called after change set traversal is finished.
274 */
275 bool SyncMediator::Commit(manifest::Manifest *manifest) {
276 reporter_->CommitReport();
277
278 if (!params_->dry_run) {
279 LogCvmfs(kLogPublish, kLogStdout,
280 "Waiting for upload of files before committing...");
281 params_->spooler->WaitForUpload();
282 }
283
284 if (!hardlink_queue_.empty()) {
285 assert(handle_hardlinks_);
286
287 LogCvmfs(kLogPublish, kLogStdout, "Processing hardlinks...");
288 params_->spooler->UnregisterListeners();
289 params_->spooler->RegisterListener(&SyncMediator::PublishHardlinksCallback,
290 this);
291
292 // TODO(rmeusel): Revise that for Thread Safety!
293 // This loop will spool hardlinks into the spooler, which will then
294 // process them.
295 // On completion of every hardlink the spooler will asynchronously
296 // emit callbacks (SyncMediator::PublishHardlinksCallback) which
297 // might happen while this for-loop goes through the hardlink_queue_
298 //
299 // For the moment this seems not to be a problem, but it's an accident
300 // just waiting to happen.
301 //
302 // Note: Just wrapping this loop in a mutex might produce a dead lock
303 // since the spooler does not fill it's processing queue to an
304 // unlimited size. Meaning that it might be flooded with hard-
305 // links and waiting for the queue to be processed while proces-
306 // sing is stalled because the callback is waiting for this
307 // mutex.
308 for (HardlinkGroupList::const_iterator i = hardlink_queue_.begin(),
309 iEnd = hardlink_queue_.end();
310 i != iEnd;
311 ++i) {
312 LogCvmfs(kLogPublish, kLogVerboseMsg, "Spooling hardlink group %s",
313 i->master->GetUnionPath().c_str());
314 IngestionSource *source = new FileIngestionSource(
315 i->master->GetUnionPath());
316 params_->spooler->Process(source);
317 }
318
319 params_->spooler->WaitForUpload();
320
321 for (HardlinkGroupList::const_iterator i = hardlink_queue_.begin(),
322 iEnd = hardlink_queue_.end();
323 i != iEnd;
324 ++i) {
325 LogCvmfs(kLogPublish, kLogVerboseMsg, "Processing hardlink group %s",
326 i->master->GetUnionPath().c_str());
327 AddHardlinkGroup(*i);
328 }
329 }
330
331 if (!bundle_specs_.empty()) {
332 LogCvmfs(kLogPublish, kLogStdout, "Processing file bundles...");
333 AddBundleSpecs();
334 }
335
336 if (union_engine_)
337 union_engine_->PostUpload();
338
339 // PostUpload may have spooled additional files (the tarball engine
340 // materializes empty files for hardlinks whose target is missing from the
341 // archive). Wait for those uploads so their catalog entries are added by
342 // the file callback before the listeners are unregistered below.
343 if (!params_->dry_run)
344 params_->spooler->WaitForUpload();
345
346 params_->spooler->UnregisterListeners();
347
348 if (params_->dry_run) {
349 manifest = NULL;
350 return true;
351 }
352
353 LogCvmfs(kLogPublish, kLogStdout, "Committing file catalogs...");
354 if (params_->spooler->GetNumberOfErrors() > 0) {
355 LogCvmfs(kLogPublish, kLogStderr, "failed to commit files");
356 return false;
357 }
358
359 if (catalog_manager_->IsBalanceable()
360 || (params_->virtual_dir_actions
361 != catalog::VirtualCatalog::kActionNone)) {
362 if (catalog_manager_->IsBalanceable())
363 catalog_manager_->Balance();
364 // Commit empty string to ensure that the "content" of the auto catalog
365 // markers is present in the repository.
366 const string empty_file = CreateTempPath(params_->dir_temp + "/empty",
367 0600);
368 IngestionSource *source = new FileIngestionSource(empty_file);
369 params_->spooler->Process(source);
370 params_->spooler->WaitForUpload();
371 unlink(empty_file.c_str());
372 if (params_->spooler->GetNumberOfErrors() > 0) {
373 LogCvmfs(kLogPublish, kLogStderr, "failed to commit auto catalog marker");
374 return false;
375 }
376 }
377 catalog_manager_->PrecalculateListings();
378 return catalog_manager_->Commit(params_->stop_for_catalog_tweaks,
379 params_->manual_revision, manifest);
380 }
381
382 std::string SyncMediator::GetBundleTriggerPath(
383 SharedPtr<SyncItem> bundle_spec_entry) const {
384 static const size_t nStrip = strlen(".cvmfsbundle-");
385 const std::string main_file_name = bundle_spec_entry->filename().substr(
386 nStrip);
387 if (main_file_name.empty()) {
388 PANIC(kLogStderr, "invalid empty bundle specification: %s",
389 bundle_spec_entry->GetUnionPath().c_str());
390 }
391 // relative_parent_path() is empty for the repo root and otherwise lacks a
392 // leading slash (e.g. "root/lib/ROOT/__pycache__"). The catalog lookup in
393 // WritableCatalogManager::FindCatalog needs an absolute path, so prepend
394 // "/" — but skip it when relative_parent_path() is empty, in which case
395 // the existing literal "/" already produces a valid "/<basename>".
396 const std::string &parent = bundle_spec_entry->relative_parent_path();
397 return (parent.empty() ? "" : "/" + parent) + "/" + main_file_name;
398 }
399
400 void SyncMediator::InsertBundleSpec(SharedPtr<SyncItem> entry) {
401 assert(entry->IsBundleSpec());
402
403 // When we leave the directory, we'll check all the bundle specs and set
404 // the corresponding flags on the main file.
405 bundle_specs_.push_back(GetBundleTriggerPath(entry));
406 }
407
408 void SyncMediator::AddBundleSpecs() {
409 if (params_->dry_run)
410 return;
411
412 for (BundleSpecs::const_iterator itr = bundle_specs_.begin();
413 itr != bundle_specs_.end();
414 ++itr) {
415 catalog_manager_->UpdateBundleTrigger(*itr, true);
416 }
417 }
418
419 void SyncMediator::InsertHardlink(SharedPtr<SyncItem> entry) {
420 assert(handle_hardlinks_);
421
422 const uint64_t inode = entry->GetUnionInode();
423 LogCvmfs(kLogPublish, kLogVerboseMsg, "found hardlink %" PRIu64 " at %s",
424 inode, entry->GetUnionPath().c_str());
425
426 // Find the hard link group in the lists
427 const HardlinkGroupMap::iterator hardlink_group = GetHardlinkMap().find(
428 inode);
429
430 if (hardlink_group == GetHardlinkMap().end()) {
431 // Create a new hardlink group
432 GetHardlinkMap().insert(
433 HardlinkGroupMap::value_type(inode, HardlinkGroup(entry)));
434 } else {
435 // Append the file to the appropriate hardlink group
436 hardlink_group->second.AddHardlink(entry);
437 }
438
439 // publish statistics counting for new file
440 if (entry->IsNew()) {
441 perf::Inc(counters_->n_files_added);
442 perf::Xadd(counters_->sz_added_bytes, entry->GetScratchSize());
443 }
444 }
445
446
447 void SyncMediator::InsertLegacyHardlink(SharedPtr<SyncItem> entry) {
448 // Check if found file has hardlinks (nlink > 1)
449 // As we are looking through all files in one directory here, there might be
450 // completely untouched hardlink groups, which we can safely skip.
451 // Finally we have to see if the hardlink is already part of this group
452
453 assert(handle_hardlinks_);
454
455 if (entry->GetUnionLinkcount() < 2)
456 return;
457
458 const uint64_t inode = entry->GetUnionInode();
459 HardlinkGroupMap::iterator hl_group;
460 hl_group = GetHardlinkMap().find(inode);
461
462 if (hl_group != GetHardlinkMap().end()) { // touched hardlinks in this group?
463 bool found = false;
464
465 // search for the entry in this group
466 for (SyncItemList::const_iterator i = hl_group->second.hardlinks.begin(),
467 iEnd = hl_group->second.hardlinks.end();
468 i != iEnd;
469 ++i) {
470 if (*(i->second) == *entry) {
471 found = true;
472 break;
473 }
474 }
475
476 if (!found) {
477 // Hardlink already in the group?
478 // If one element of a hardlink group is edited, all elements must be
479 // replaced. Here, we remove an untouched hardlink and add it to its
480 // hardlink group for re-adding later
481 LogCvmfs(kLogPublish, kLogVerboseMsg, "Picked up legacy hardlink %s",
482 entry->GetUnionPath().c_str());
483 Remove(entry);
484 hl_group->second.AddHardlink(entry);
485 }
486 }
487 }
488
489
490 /**
491 * Create a recursion engine which DOES NOT recurse into directories.
492 * It basically goes through the current directory (in the union volume) and
493 * searches for legacy hardlinks which has to be connected to the new
494 * or edited ones.
495 */
496 void SyncMediator::CompleteHardlinks(SharedPtr<SyncItem> entry) {
497 assert(handle_hardlinks_);
498
499 // If no hardlink in this directory was changed, we can skip this
500 if (GetHardlinkMap().empty())
501 return;
502
503 LogCvmfs(kLogPublish, kLogVerboseMsg, "Post-processing hard links in %s",
504 entry->GetUnionPath().c_str());
505
506 // Look for legacy hardlinks
507 FileSystemTraversal<SyncMediator> traversal(this, union_engine_->union_path(),
508 false);
509 traversal.fn_new_file = &SyncMediator::LegacyRegularHardlinkCallback;
510 traversal.fn_new_symlink = &SyncMediator::LegacySymlinkHardlinkCallback;
511 traversal.fn_new_character_dev = &SyncMediator::
512 LegacyCharacterDeviceHardlinkCallback;
513 traversal.fn_new_block_dev = &SyncMediator::LegacyBlockDeviceHardlinkCallback;
514 traversal.fn_new_fifo = &SyncMediator::LegacyFifoHardlinkCallback;
515 traversal.fn_new_socket = &SyncMediator::LegacySocketHardlinkCallback;
516 traversal.Recurse(entry->GetUnionPath());
517 }
518
519
520 void SyncMediator::LegacyRegularHardlinkCallback(const string &parent_dir,
521 const string &file_name) {
522 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
523 kItemFile);
524 InsertLegacyHardlink(entry);
525 }
526
527
528 void SyncMediator::LegacySymlinkHardlinkCallback(const string &parent_dir,
529 const string &file_name) {
530 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
531 kItemSymlink);
532 InsertLegacyHardlink(entry);
533 }
534
535 void SyncMediator::LegacyCharacterDeviceHardlinkCallback(
536 const string &parent_dir, const string &file_name) {
537 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
538 kItemCharacterDevice);
539 InsertLegacyHardlink(entry);
540 }
541
542 void SyncMediator::LegacyBlockDeviceHardlinkCallback(const string &parent_dir,
543 const string &file_name) {
544 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
545 kItemBlockDevice);
546 InsertLegacyHardlink(entry);
547 }
548
549 void SyncMediator::LegacyFifoHardlinkCallback(const string &parent_dir,
550 const string &file_name) {
551 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
552 kItemFifo);
553 InsertLegacyHardlink(entry);
554 }
555
556 void SyncMediator::LegacySocketHardlinkCallback(const string &parent_dir,
557 const string &file_name) {
558 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
559 kItemSocket);
560 InsertLegacyHardlink(entry);
561 }
562
563
564 void SyncMediator::AddDirectoryRecursively(SharedPtr<SyncItem> entry) {
565 AddDirectory(entry);
566
567 // Create a recursion engine, which recursively adds all entries in a newly
568 // created directory
569 FileSystemTraversal<SyncMediator> traversal(
570 this, union_engine_->scratch_path(), true);
571 traversal.fn_enter_dir = &SyncMediator::EnterAddedDirectoryCallback;
572 traversal.fn_leave_dir = &SyncMediator::LeaveAddedDirectoryCallback;
573 traversal.fn_new_file = &SyncMediator::AddFileCallback;
574 traversal.fn_new_symlink = &SyncMediator::AddSymlinkCallback;
575 traversal.fn_new_dir_prefix = &SyncMediator::AddDirectoryCallback;
576 traversal.fn_ignore_file = &SyncMediator::IgnoreFileCallback;
577 traversal.fn_new_character_dev = &SyncMediator::AddCharacterDeviceCallback;
578 traversal.fn_new_block_dev = &SyncMediator::AddBlockDeviceCallback;
579 traversal.fn_new_fifo = &SyncMediator::AddFifoCallback;
580 traversal.fn_new_socket = &SyncMediator::AddSocketCallback;
581 traversal.Recurse(entry->GetScratchPath());
582 }
583
584
585 bool SyncMediator::AddDirectoryCallback(const std::string &parent_dir,
586 const std::string &dir_name) {
587 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, dir_name,
588 kItemDir);
589 AddDirectory(entry);
590 return true; // The recursion engine should recurse deeper here
591 }
592
593
594 void SyncMediator::AddFileCallback(const std::string &parent_dir,
595 const std::string &file_name) {
596 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
597 kItemFile);
598 Add(entry);
599 }
600
601
602 void SyncMediator::AddCharacterDeviceCallback(const std::string &parent_dir,
603 const std::string &file_name) {
604 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
605 kItemCharacterDevice);
606 Add(entry);
607 }
608
609 void SyncMediator::AddBlockDeviceCallback(const std::string &parent_dir,
610 const std::string &file_name) {
611 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
612 kItemBlockDevice);
613 Add(entry);
614 }
615
616 void SyncMediator::AddFifoCallback(const std::string &parent_dir,
617 const std::string &file_name) {
618 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
619 kItemFifo);
620 Add(entry);
621 }
622
623 void SyncMediator::AddSocketCallback(const std::string &parent_dir,
624 const std::string &file_name) {
625 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
626 kItemSocket);
627 Add(entry);
628 }
629
630 void SyncMediator::AddSymlinkCallback(const std::string &parent_dir,
631 const std::string &link_name) {
632 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, link_name,
633 kItemSymlink);
634 Add(entry);
635 }
636
637
638 void SyncMediator::EnterAddedDirectoryCallback(const std::string &parent_dir,
639 const std::string &dir_name) {
640 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, dir_name,
641 kItemDir);
642 EnterDirectory(entry);
643 }
644
645
646 void SyncMediator::LeaveAddedDirectoryCallback(const std::string &parent_dir,
647 const std::string &dir_name) {
648 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, dir_name,
649 kItemDir);
650 LeaveDirectory(entry);
651 }
652
653
654 void SyncMediator::RemoveDirectoryRecursively(SharedPtr<SyncItem> entry,
655 bool fast_delete) {
656 const std::string directory_path = entry->GetRelativePath();
657
658 // Fast delete: skip filesystem traversal for nested catalog directories.
659 // Instead of recursively walking the filesystem and removing each entry,
660 // we just remove the nested catalog reference from the parent catalog
661 // (with merge=false so entries are not copied to the parent) and then
662 // remove the mountpoint directory entry.
663 if (fast_delete && catalog_manager_->IsTransitionPoint(directory_path)) {
664 // Get the nested catalog's counters before removal so we can update
665 // publish statistics with the total number of removed entries. Because the
666 // filesystem is not walked, these aggregate counters (self + subtree) are
667 // the only source for the removal statistics. self.directories already
668 // accounts for the nested catalog root, which is the same filesystem
669 // directory as the mountpoint removed from the parent below, so it must not
670 // be counted again (the normal traversal path also counts it exactly once).
671 // Likewise, every descendant nested catalog is represented twice in the
672 // aggregate counters (mountpoint + root), so subtract the nested catalog
673 // count from the aggregate directory count below.
674 std::string subcatalog_path;
675 shash::Any hash;
676 // LookupCounters expects an absolute catalog path (leading slash); the sync
677 // item's relative path does not carry one, so prepend it here. Without the
678 // slash the prefix match falls back to the root catalog and the removal
679 // statistics would account for the whole repository instead of the subtree.
680 const std::string absolute_path = "/" + directory_path;
681 PathString ps_path;
682 ps_path.Assign(absolute_path.data(), absolute_path.length());
683 const catalog::Counters counters = catalog_manager_->LookupCounters(
684 ps_path, &subcatalog_path, &hash);
685 // On failure to load the subtree LookupCounters returns zeroed counters and
686 // a null hash. Removal proceeds regardless, but the statistics would
687 // silently under-count, so warn.
688 if (hash.IsNull()) {
689 LogCvmfs(kLogPublish, kLogStderr | kLogSyslogWarn,
690 "Warning: could not read counters of nested catalog at '%s'; "
691 "removal statistics may be incomplete",
692 directory_path.c_str());
693 }
694 {
695 perf::Xadd(counters_->n_files_removed,
696 static_cast<int64_t>(counters.self.regular_files
697 + counters.subtree.regular_files));
698 const uint64_t n_nested_catalogs = counters.self.nested_catalogs
699 + counters.subtree.nested_catalogs;
700 const uint64_t n_directories = counters.self.directories
701 + counters.subtree.directories;
702 perf::Xadd(counters_->n_directories_removed,
703 static_cast<int64_t>(n_directories - n_nested_catalogs));
704 perf::Xadd(counters_->n_symlinks_removed,
705 static_cast<int64_t>(counters.self.symlinks
706 + counters.subtree.symlinks));
707 perf::Xadd(counters_->sz_removed_bytes,
708 static_cast<int64_t>(counters.self.file_size
709 + counters.subtree.file_size));
710 }
711
712 // Remove nested catalog (merge=false: just remove the reference and
713 // adjust parent subtree counters, don't copy entries into parent)
714 const std::string notice = "Nested catalog at " + entry->GetUnionPath();
715 reporter_->OnRemove(notice, catalog::DirectoryEntry());
716 if (!params_->dry_run) {
717 catalog_manager_->RemoveNestedCatalog(directory_path, false);
718 }
719
720 // Remove the mountpoint directory entry from the parent catalog
721 reporter_->OnRemove(entry->GetUnionPath(), catalog::DirectoryEntry());
722 if (!params_->dry_run) {
723 catalog_manager_->RemoveDirectory(directory_path);
724 }
725
726 return;
727 }
728
729 // Normal path: delete a directory AFTER it was emptied here,
730 // because it would start up another recursion.
731 // Propagate fast_delete so that any nested catalog transition points
732 // encountered during the traversal are still fast-deleted.
733 const bool prev_fast_delete = recursive_fast_delete_;
734 if (fast_delete)
735 recursive_fast_delete_ = true;
736
737 const bool recurse = false;
738 FileSystemTraversal<SyncMediator> traversal(
739 this, union_engine_->rdonly_path(), recurse);
740 traversal.fn_new_file = &SyncMediator::RemoveFileCallback;
741 traversal.fn_new_dir_postfix = &SyncMediator::RemoveDirectoryCallback;
742 traversal.fn_new_symlink = &SyncMediator::RemoveSymlinkCallback;
743 traversal.fn_new_character_dev = &SyncMediator::RemoveCharacterDeviceCallback;
744 traversal.fn_new_block_dev = &SyncMediator::RemoveBlockDeviceCallback;
745 traversal.fn_new_fifo = &SyncMediator::RemoveFifoCallback;
746 traversal.fn_new_socket = &SyncMediator::RemoveSocketCallback;
747 traversal.Recurse(entry->GetRdOnlyPath());
748
749 recursive_fast_delete_ = prev_fast_delete;
750
751 // The given directory was emptied recursively and can now itself be deleted
752 RemoveDirectory(entry);
753 }
754
755
756 void SyncMediator::RemoveFileCallback(const std::string &parent_dir,
757 const std::string &file_name) {
758 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
759 kItemFile);
760 Remove(entry);
761 }
762
763
764 void SyncMediator::RemoveSymlinkCallback(const std::string &parent_dir,
765 const std::string &link_name) {
766 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, link_name,
767 kItemSymlink);
768 Remove(entry);
769 }
770
771 void SyncMediator::RemoveCharacterDeviceCallback(const std::string &parent_dir,
772 const std::string &link_name) {
773 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, link_name,
774 kItemCharacterDevice);
775 Remove(entry);
776 }
777
778 void SyncMediator::RemoveBlockDeviceCallback(const std::string &parent_dir,
779 const std::string &link_name) {
780 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, link_name,
781 kItemBlockDevice);
782 Remove(entry);
783 }
784
785 void SyncMediator::RemoveFifoCallback(const std::string &parent_dir,
786 const std::string &link_name) {
787 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, link_name,
788 kItemFifo);
789 Remove(entry);
790 }
791
792 void SyncMediator::RemoveSocketCallback(const std::string &parent_dir,
793 const std::string &link_name) {
794 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, link_name,
795 kItemSocket);
796 Remove(entry);
797 }
798
799 void SyncMediator::RemoveDirectoryCallback(const std::string &parent_dir,
800 const std::string &dir_name) {
801 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, dir_name,
802 kItemDir);
803 RemoveDirectoryRecursively(entry, recursive_fast_delete_);
804 }
805
806
807 bool SyncMediator::IgnoreFileCallback(const std::string &parent_dir,
808 const std::string &file_name) {
809 if (union_engine_->IgnoreFilePredicate(parent_dir, file_name)) {
810 return true;
811 }
812
813 const SharedPtr<SyncItem> entry = CreateSyncItem(parent_dir, file_name,
814 kItemUnknown);
815 return entry->IsWhiteout();
816 }
817
818 SharedPtr<SyncItem> SyncMediator::CreateSyncItem(
819 const std::string &relative_parent_path, const std::string &filename,
820 const SyncItemType entry_type) const {
821 return union_engine_->CreateSyncItem(relative_parent_path, filename,
822 entry_type);
823 }
824
825 void SyncMediator::PublishFilesCallback(const upload::SpoolerResult &result) {
826 LogCvmfs(kLogPublish, kLogVerboseMsg,
827 "Spooler callback for %s, digest %s, produced %lu chunks, retval %d",
828 result.local_path.c_str(), result.content_hash.ToString().c_str(),
829 result.file_chunks.size(), result.return_code);
830 if (result.return_code != 0) {
831 PANIC(kLogStderr, "Spool failure for %s (%d)", result.local_path.c_str(),
832 result.return_code);
833 }
834
835 SyncItemList::iterator itr;
836 {
837 const MutexLockGuard guard(lock_file_queue_);
838 itr = file_queue_.find(result.local_path);
839 }
840
841 assert(itr != file_queue_.end());
842
843 SyncItem &item = *itr->second;
844 item.SetContentHash(result.content_hash);
845 item.SetCompressionAlgorithm(result.compression_alg);
846
847 XattrList *xattrs = &default_xattrs_;
848 if (params_->include_xattrs) {
849 xattrs = XattrList::CreateFromFile(result.local_path);
850 assert(xattrs != NULL);
851 }
852
853 if (result.IsChunked()) {
854 catalog_manager_->AddChunkedFile(
855 item.CreateBasicCatalogDirent(params_->enable_mtime_ns),
856 *xattrs,
857 item.relative_parent_path(),
858 result.file_chunks);
859 } else {
860 catalog_manager_->AddFile(
861 item.CreateBasicCatalogDirent(params_->enable_mtime_ns),
862 *xattrs,
863 item.relative_parent_path());
864 }
865
866 if (xattrs != &default_xattrs_)
867 free(xattrs);
868 }
869
870
871 void SyncMediator::PublishHardlinksCallback(
872 const upload::SpoolerResult &result) {
873 LogCvmfs(kLogPublish, kLogVerboseMsg,
874 "Spooler callback for hardlink %s, digest %s, retval %d",
875 result.local_path.c_str(), result.content_hash.ToString().c_str(),
876 result.return_code);
877 if (result.return_code != 0) {
878 PANIC(kLogStderr, "Spool failure for %s (%d)", result.local_path.c_str(),
879 result.return_code);
880 }
881
882 bool found = false;
883 for (unsigned i = 0; i < hardlink_queue_.size(); ++i) {
884 if (hardlink_queue_[i].master->GetUnionPath() == result.local_path) {
885 found = true;
886 hardlink_queue_[i].master->SetContentHash(result.content_hash);
887 SyncItemList::iterator j, jend;
888 for (j = hardlink_queue_[i].hardlinks.begin(),
889 jend = hardlink_queue_[i].hardlinks.end();
890 j != jend;
891 ++j) {
892 j->second->SetContentHash(result.content_hash);
893 j->second->SetCompressionAlgorithm(result.compression_alg);
894 }
895 if (result.IsChunked())
896 hardlink_queue_[i].file_chunks = result.file_chunks;
897
898 break;
899 }
900 }
901
902 assert(found);
903 }
904
905
906 void SyncMediator::CreateNestedCatalog(SharedPtr<SyncItem> directory) {
907 const std::string notice = "Nested catalog at " + directory->GetUnionPath();
908 reporter_->OnAdd(notice, catalog::DirectoryEntry());
909
910 if (!params_->dry_run) {
911 catalog_manager_->CreateNestedCatalog(directory->GetRelativePath());
912 }
913 }
914
915
916 void SyncMediator::RemoveNestedCatalog(SharedPtr<SyncItem> directory) {
917 const std::string notice = "Nested catalog at " + directory->GetUnionPath();
918 reporter_->OnRemove(notice, catalog::DirectoryEntry());
919
920 if (!params_->dry_run) {
921 catalog_manager_->RemoveNestedCatalog(directory->GetRelativePath());
922 }
923 }
924
925 void SyncDiffReporter::OnInit(const history::History::Tag & /*from_tag*/,
926 const history::History::Tag & /*to_tag*/) { }
927
928 void SyncDiffReporter::OnStats(const catalog::DeltaCounters & /*delta*/) { }
929
930 void SyncDiffReporter::OnAdd(const std::string &path,
931 const catalog::DirectoryEntry & /*entry*/) {
932 changed_items_++;
933 AddImpl(path);
934 }
935 void SyncDiffReporter::OnRemove(const std::string &path,
936 const catalog::DirectoryEntry & /*entry*/) {
937 changed_items_++;
938 RemoveImpl(path);
939 }
940 void SyncDiffReporter::OnModify(const std::string &path,
941 const catalog::DirectoryEntry & /*entry_from*/,
942 const catalog::DirectoryEntry & /*entry_to*/) {
943 changed_items_++;
944 ModifyImpl(path);
945 }
946
947 void SyncDiffReporter::CommitReport() {
948 if (print_action_ == kPrintDots) {
949 if (changed_items_ >= processing_dot_interval_) {
950 LogCvmfs(kLogPublish, kLogStdout | kLogNoLinebreak, "\n");
951 }
952 }
953 }
954
955 void SyncDiffReporter::PrintDots() {
956 if (changed_items_ % processing_dot_interval_ == 0) {
957 LogCvmfs(kLogPublish, kLogStdout | kLogNoLinebreak, ".");
958 }
959 }
960
961 void SyncDiffReporter::AddImpl(const std::string &path) {
962 const char *action_label;
963
964 switch (print_action_) {
965 case kPrintChanges:
966 if (path.at(0) != '/') {
967 action_label = "[x-catalog-add]";
968 } else {
969 action_label = "[add]";
970 }
971 LogCvmfs(kLogPublish, kLogStdout, "%s %s", action_label, path.c_str());
972 break;
973
974 case kPrintDots:
975 PrintDots();
976 break;
977 default:
978 assert("Invalid print action.");
979 }
980 }
981
982 void SyncDiffReporter::RemoveImpl(const std::string &path) {
983 const char *action_label;
984
985 switch (print_action_) {
986 case kPrintChanges:
987 if (path.at(0) != '/') {
988 action_label = "[x-catalog-rem]";
989 } else {
990 action_label = "[rem]";
991 }
992
993 LogCvmfs(kLogPublish, kLogStdout, "%s %s", action_label, path.c_str());
994 break;
995
996 case kPrintDots:
997 PrintDots();
998 break;
999 default:
1000 assert("Invalid print action.");
1001 }
1002 }
1003
1004 void SyncDiffReporter::ModifyImpl(const std::string &path) {
1005 const char *action_label;
1006
1007 switch (print_action_) {
1008 case kPrintChanges:
1009 action_label = "[mod]";
1010 LogCvmfs(kLogPublish, kLogStdout, "%s %s", action_label, path.c_str());
1011 break;
1012
1013 case kPrintDots:
1014 PrintDots();
1015 break;
1016 default:
1017 assert("Invalid print action.");
1018 }
1019 }
1020
1021 void SyncMediator::AddFile(SharedPtr<SyncItem> entry) {
1022 reporter_->OnAdd(entry->GetUnionPath(), catalog::DirectoryEntry());
1023
1024 if ((entry->IsSymlink() || entry->IsSpecialFile()) && !params_->dry_run) {
1025 assert(!entry->HasGraftMarker());
1026 // Symlinks and special files are completely stored in the catalog
1027 XattrList *xattrs = &default_xattrs_;
1028 if (params_->include_xattrs) {
1029 xattrs = XattrList::CreateFromFile(entry->GetUnionPath());
1030 assert(xattrs);
1031 }
1032 catalog_manager_->AddFile(
1033 entry->CreateBasicCatalogDirent(params_->enable_mtime_ns),
1034 *xattrs,
1035 entry->relative_parent_path());
1036 if (xattrs != &default_xattrs_)
1037 free(xattrs);
1038 } else if (entry->HasGraftMarker() && !params_->dry_run) {
1039 if (entry->IsValidGraft()) {
1040 // Graft files are added to catalog immediately.
1041 if (entry->IsChunkedGraft()) {
1042 catalog_manager_->AddChunkedFile(
1043 entry->CreateBasicCatalogDirent(params_->enable_mtime_ns),
1044 default_xattrs_,
1045 entry->relative_parent_path(),
1046 *(entry->GetGraftChunks()));
1047 } else {
1048 catalog_manager_->AddFile(
1049 entry->CreateBasicCatalogDirent(params_->enable_mtime_ns),
1050 default_xattrs_, // TODO(bbockelm): For now, use default xattrs
1051 // on grafted files.
1052 entry->relative_parent_path());
1053 }
1054 } else {
1055 // Unlike with regular files, grafted files can be "unpublishable" - i.e.,
1056 // the graft file is missing information. It's not clear that continuing
1057 // forward with the publish is the correct thing to do; abort for now.
1058 PANIC(kLogStderr,
1059 "Encountered a grafted file (%s) with "
1060 "invalid grafting information; check contents of .cvmfsgraft-*"
1061 " file. Aborting publish.",
1062 entry->GetRelativePath().c_str());
1063 }
1064 } else if (entry->relative_parent_path().empty()
1065 && entry->IsCatalogMarker()) {
1066 PANIC(kLogStderr, "Error: nested catalog marker in root directory");
1067 } else if (!params_->dry_run) {
1068 {
1069 // Push the file to the spooler, remember the entry for the path
1070 const MutexLockGuard m(&lock_file_queue_);
1071 file_queue_[entry->GetUnionPath()] = entry;
1072 }
1073 // Spool the file
1074 params_->spooler->Process(entry->CreateIngestionSource());
1075 }
1076
1077 // publish statistics counting for new file
1078 if (entry->IsNew()) {
1079 if (entry->IsSymlink()) {
1080 perf::Inc(counters_->n_symlinks_added);
1081 } else {
1082 perf::Inc(counters_->n_files_added);
1083 perf::Xadd(counters_->sz_added_bytes, entry->GetScratchSize());
1084 }
1085 }
1086 }
1087
1088 void SyncMediator::RemoveFile(SharedPtr<SyncItem> entry) {
1089 reporter_->OnRemove(entry->GetUnionPath(), catalog::DirectoryEntry());
1090
1091 if (!params_->dry_run) {
1092 if (handle_hardlinks_ && entry->GetRdOnlyLinkcount() > 1) {
1093 LogCvmfs(kLogPublish, kLogVerboseMsg, "remove %s from hardlink group",
1094 entry->GetUnionPath().c_str());
1095 catalog_manager_->ShrinkHardlinkGroup(entry->GetRelativePath());
1096 }
1097 catalog_manager_->RemoveFile(entry->GetRelativePath());
1098 }
1099
1100 // Counting nr of removed files and removed bytes
1101 if (entry->WasSymlink()) {
1102 perf::Inc(counters_->n_symlinks_removed);
1103 } else {
1104 perf::Inc(counters_->n_files_removed);
1105 }
1106 perf::Xadd(counters_->sz_removed_bytes, entry->GetRdOnlySize());
1107 }
1108
1109 void SyncMediator::AddUnmaterializedDirectory(SharedPtr<SyncItem> entry) {
1110 AddDirectory(entry);
1111 }
1112
1113 void SyncMediator::AddDirectory(SharedPtr<SyncItem> entry) {
1114 if (entry->IsBundleSpec()) {
1115 PANIC(kLogStderr,
1116 "Illegal directory name: %s. "
1117 "The .cvmfsbundle- prefix is reserved for bundles specifications",
1118 entry->GetUnionPath().c_str());
1119 }
1120
1121 reporter_->OnAdd(entry->GetUnionPath(), catalog::DirectoryEntry());
1122
1123 perf::Inc(counters_->n_directories_added);
1124 assert(!entry->HasGraftMarker());
1125 if (!params_->dry_run) {
1126 XattrList *xattrs = &default_xattrs_;
1127 if (params_->include_xattrs) {
1128 xattrs = XattrList::CreateFromFile(entry->GetUnionPath());
1129 assert(xattrs);
1130 }
1131 catalog_manager_->AddDirectory(
1132 entry->CreateBasicCatalogDirent(params_->enable_mtime_ns), *xattrs,
1133 entry->relative_parent_path());
1134 if (xattrs != &default_xattrs_)
1135 free(xattrs);
1136 }
1137
1138 if (entry->HasCatalogMarker()
1139 && !catalog_manager_->IsTransitionPoint(entry->GetRelativePath())) {
1140 CreateNestedCatalog(entry);
1141 }
1142 }
1143
1144
1145 /**
1146 * this method deletes a single directory entry! Make sure to empty it
1147 * before you call this method or simply use
1148 * SyncMediator::RemoveDirectoryRecursively instead.
1149 */
1150 void SyncMediator::RemoveDirectory(SharedPtr<SyncItem> entry) {
1151 const std::string directory_path = entry->GetRelativePath();
1152
1153 if (catalog_manager_->IsTransitionPoint(directory_path)) {
1154 RemoveNestedCatalog(entry);
1155 }
1156
1157 reporter_->OnRemove(entry->GetUnionPath(), catalog::DirectoryEntry());
1158 if (!params_->dry_run) {
1159 catalog_manager_->RemoveDirectory(directory_path);
1160 }
1161
1162 perf::Inc(counters_->n_directories_removed);
1163 }
1164
1165 void SyncMediator::TouchDirectory(SharedPtr<SyncItem> entry) {
1166 reporter_->OnModify(entry->GetUnionPath(), catalog::DirectoryEntry(),
1167 catalog::DirectoryEntry());
1168
1169 const std::string directory_path = entry->GetRelativePath();
1170
1171 if (!params_->dry_run) {
1172 XattrList *xattrs = &default_xattrs_;
1173 if (params_->include_xattrs) {
1174 xattrs = XattrList::CreateFromFile(entry->GetUnionPath());
1175 assert(xattrs);
1176 }
1177 catalog_manager_->TouchDirectory(
1178 entry->CreateBasicCatalogDirent(params_->enable_mtime_ns), *xattrs,
1179 directory_path);
1180 if (xattrs != &default_xattrs_)
1181 free(xattrs);
1182 }
1183
1184 if (entry->HasCatalogMarker()
1185 && !catalog_manager_->IsTransitionPoint(directory_path)) {
1186 CreateNestedCatalog(entry);
1187 } else if (!entry->HasCatalogMarker()
1188 && catalog_manager_->IsTransitionPoint(directory_path)) {
1189 RemoveNestedCatalog(entry);
1190 }
1191 }
1192
1193 /**
1194 * All hardlinks in the current directory have been picked up. Now they are
1195 * added to the catalogs.
1196 */
1197 void SyncMediator::AddLocalHardlinkGroups(const HardlinkGroupMap &hardlinks) {
1198 assert(handle_hardlinks_);
1199
1200 for (HardlinkGroupMap::const_iterator i = hardlinks.begin(),
1201 iEnd = hardlinks.end();
1202 i != iEnd;
1203 ++i) {
1204 if (i->second.hardlinks.size() != i->second.master->GetUnionLinkcount()
1205 && !params_->ignore_xdir_hardlinks) {
1206 PANIC(kLogSyslogErr | kLogDebug, "Hardlinks across directories (%s)",
1207 i->second.master->GetUnionPath().c_str());
1208 }
1209
1210 if (params_->print_changeset) {
1211 for (SyncItemList::const_iterator j = i->second.hardlinks.begin(),
1212 jEnd = i->second.hardlinks.end();
1213 j != jEnd;
1214 ++j) {
1215 const std::string changeset_notice = GetParentPath(i->second.master
1216 ->GetUnionPath())
1217 + "/" + j->second->filename();
1218 reporter_->OnAdd(changeset_notice, catalog::DirectoryEntry());
1219 }
1220 }
1221
1222 if (params_->dry_run)
1223 continue;
1224
1225 if (i->second.master->IsSymlink() || i->second.master->IsSpecialFile())
1226 AddHardlinkGroup(i->second);
1227 else
1228 hardlink_queue_.push_back(i->second);
1229 }
1230 }
1231
1232
1233 void SyncMediator::AddHardlinkGroup(const HardlinkGroup &group) {
1234 assert(handle_hardlinks_);
1235
1236 // Create a DirectoryEntry list out of the hardlinks
1237 catalog::DirectoryEntryBaseList hardlinks;
1238 for (SyncItemList::const_iterator i = group.hardlinks.begin(),
1239 iEnd = group.hardlinks.end();
1240 i != iEnd;
1241 ++i) {
1242 hardlinks.push_back(
1243 i->second->CreateBasicCatalogDirent(params_->enable_mtime_ns));
1244 }
1245 XattrList *xattrs = &default_xattrs_;
1246 if (params_->include_xattrs) {
1247 xattrs = XattrList::CreateFromFile(group.master->GetUnionPath());
1248 assert(xattrs);
1249 }
1250 catalog_manager_->AddHardlinkGroup(hardlinks,
1251 *xattrs,
1252 group.master->relative_parent_path(),
1253 group.file_chunks);
1254 if (xattrs != &default_xattrs_)
1255 free(xattrs);
1256 }
1257
1258 } // namespace publish
1259