GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/sync_mediator.cc
Date: 2026-10-04 02:40:33
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 552 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