| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/catalog_diff_tool_impl.h |
| Date: | 2026-08-23 02:40:52 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 113 | 134 | 84.3% |
| Branches: | 106 | 210 | 50.5% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * This file is part of the CernVM File System. | ||
| 3 | */ | ||
| 4 | |||
| 5 | #ifndef CVMFS_CATALOG_DIFF_TOOL_IMPL_H_ | ||
| 6 | #define CVMFS_CATALOG_DIFF_TOOL_IMPL_H_ | ||
| 7 | |||
| 8 | #include <algorithm> | ||
| 9 | #include <string> | ||
| 10 | |||
| 11 | // clang-format off | ||
| 12 | // Only needed to let clang-tidy see the class definitions. | ||
| 13 | // This would be an include loop if not for the header guard. | ||
| 14 | #include "catalog_diff_tool.h" | ||
| 15 | // clang-format on | ||
| 16 | |||
| 17 | #include "catalog.h" | ||
| 18 | #include "crypto/hash.h" | ||
| 19 | #include "network/download.h" | ||
| 20 | #include "util/exception.h" | ||
| 21 | #include "util/logging.h" | ||
| 22 | #include "util/posix.h" | ||
| 23 | |||
| 24 | const uint64_t kLastInode = uint64_t(-1); | ||
| 25 | |||
| 26 | 592 | inline void AppendFirstEntry(catalog::DirectoryEntryList *entry_list) { | |
| 27 |
1/2✓ Branch 1 taken 592 times.
✗ Branch 2 not taken.
|
592 | const catalog::DirectoryEntry empty_entry; |
| 28 |
1/2✓ Branch 1 taken 592 times.
✗ Branch 2 not taken.
|
592 | entry_list->push_back(empty_entry); |
| 29 | 592 | } | |
| 30 | |||
| 31 | 592 | inline void AppendLastEntry(catalog::DirectoryEntryList *entry_list) { | |
| 32 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 592 times.
|
592 | assert(!entry_list->empty()); |
| 33 |
1/2✓ Branch 1 taken 592 times.
✗ Branch 2 not taken.
|
592 | catalog::DirectoryEntry last_entry; |
| 34 | 592 | last_entry.set_inode(kLastInode); | |
| 35 |
1/2✓ Branch 1 taken 592 times.
✗ Branch 2 not taken.
|
592 | entry_list->push_back(last_entry); |
| 36 | 592 | } | |
| 37 | |||
| 38 | 3182 | inline bool IsSmaller(const catalog::DirectoryEntry &a, | |
| 39 | const catalog::DirectoryEntry &b) { | ||
| 40 | 3182 | const bool a_is_first = (a.inode() | |
| 41 | 3182 | == catalog::DirectoryEntryBase::kInvalidInode); | |
| 42 | 3182 | const bool a_is_last = (a.inode() == kLastInode); | |
| 43 | 3182 | const bool b_is_first = (b.inode() | |
| 44 | 3182 | == catalog::DirectoryEntryBase::kInvalidInode); | |
| 45 | 3182 | const bool b_is_last = (b.inode() == kLastInode); | |
| 46 | |||
| 47 |
4/4✓ Branch 0 taken 2516 times.
✓ Branch 1 taken 666 times.
✓ Branch 2 taken 1628 times.
✓ Branch 3 taken 888 times.
|
3182 | if (a_is_last || b_is_first) |
| 48 | 2294 | return false; | |
| 49 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 888 times.
|
888 | if (a_is_first) |
| 50 | ✗ | return !b_is_first; | |
| 51 |
2/2✓ Branch 0 taken 148 times.
✓ Branch 1 taken 740 times.
|
888 | if (b_is_last) |
| 52 | 148 | return !a_is_last; | |
| 53 |
1/2✓ Branch 2 taken 740 times.
✗ Branch 3 not taken.
|
740 | return a.name() < b.name(); |
| 54 | } | ||
| 55 | |||
| 56 | template<typename RoCatalogMgr> | ||
| 57 | 74 | bool CatalogDiffTool<RoCatalogMgr>::Init() { | |
| 58 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | if (needs_setup_) { |
| 59 | // Create a temp directory | ||
| 60 | 74 | old_raii_temp_dir_ = std::unique_ptr<RaiiTempDir>( | |
| 61 | 74 | RaiiTempDir::Create(temp_dir_prefix_)); | |
| 62 | 74 | new_raii_temp_dir_ = std::unique_ptr<RaiiTempDir>( | |
| 63 | 74 | RaiiTempDir::Create(temp_dir_prefix_)); | |
| 64 | |||
| 65 | // Old catalog from release manager machine (before lease) | ||
| 66 | 148 | old_catalog_mgr_ = std::unique_ptr<RoCatalogMgr>(OpenCatalogManager( | |
| 67 |
1/2✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
|
74 | repo_path_, old_raii_temp_dir_->dir(), old_root_hash_, |
| 68 | 74 | download_manager_, &stats_old_, cache_dir_)); | |
| 69 | |||
| 70 | // New catalog from release manager machine (before lease) | ||
| 71 | 148 | new_catalog_mgr_ = std::unique_ptr<RoCatalogMgr>(OpenCatalogManager( | |
| 72 |
1/2✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
|
74 | repo_path_, new_raii_temp_dir_->dir(), new_root_hash_, |
| 73 | 74 | download_manager_, &stats_new_, cache_dir_)); | |
| 74 | |||
| 75 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
|
74 | if (old_catalog_mgr_.get() == nullptr) { |
| 76 | ✗ | LogCvmfs(kLogCvmfs, kLogStderr, "Could not open old catalog"); | |
| 77 | ✗ | return false; | |
| 78 | } | ||
| 79 | |||
| 80 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
|
74 | if (new_catalog_mgr_.get() == nullptr) { |
| 81 | ✗ | LogCvmfs(kLogCvmfs, kLogStderr, "Could not open new catalog"); | |
| 82 | ✗ | return false; | |
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | 74 | return true; | |
| 87 | } | ||
| 88 | |||
| 89 | template<typename RoCatalogMgr> | ||
| 90 | 74 | bool CatalogDiffTool<RoCatalogMgr>::Run(const PathString &path) { | |
| 91 | 74 | DiffRec(path); | |
| 92 | |||
| 93 | 74 | return true; | |
| 94 | } | ||
| 95 | |||
| 96 | template<typename RoCatalogMgr> | ||
| 97 | 148 | RoCatalogMgr *CatalogDiffTool<RoCatalogMgr>::OpenCatalogManager( | |
| 98 | const std::string &repo_path, const std::string &temp_dir, | ||
| 99 | const shash::Any &root_hash, download::DownloadManager *download_manager, | ||
| 100 | perf::Statistics *stats, const std::string &cache_dir) { | ||
| 101 |
1/2✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
|
148 | RoCatalogMgr *mgr = new RoCatalogMgr( |
| 102 | root_hash, repo_path, temp_dir, download_manager, stats, true, cache_dir); | ||
| 103 | 148 | mgr->Init(); | |
| 104 | |||
| 105 | 148 | return mgr; | |
| 106 | } | ||
| 107 | |||
| 108 | template<typename RoCatalogMgr> | ||
| 109 | 296 | void CatalogDiffTool<RoCatalogMgr>::DiffRec(const PathString &path) { | |
| 110 | // Terminate recursion upon reaching an ignored path | ||
| 111 |
2/4✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 296 times.
|
296 | if (IsIgnoredPath(path)) { |
| 112 | ✗ | assert(!IsReportablePath(path)); | |
| 113 | ✗ | return; | |
| 114 | } | ||
| 115 | |||
| 116 |
2/4✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 296 times.
✗ Branch 6 not taken.
|
296 | LogCvmfs(kLogReceiver, kLogDebug, "DiffRec: recursing into %s", |
| 117 | path.ToString().c_str()); | ||
| 118 | |||
| 119 | 296 | catalog::DirectoryEntryList old_listing; | |
| 120 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | AppendFirstEntry(&old_listing); |
| 121 |
1/2✓ Branch 2 taken 296 times.
✗ Branch 3 not taken.
|
296 | old_catalog_mgr_->Listing(path, &old_listing); |
| 122 |
1/2✓ Branch 3 taken 296 times.
✗ Branch 4 not taken.
|
296 | sort(old_listing.begin(), old_listing.end(), IsSmaller); |
| 123 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | AppendLastEntry(&old_listing); |
| 124 | |||
| 125 | // create these paths here so it can be "reused" in the loop without | ||
| 126 | // re-initializing every time, this should save time in doing memcpy | ||
| 127 | // especially when the path gets longer | ||
| 128 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | PathString old_path(path); |
| 129 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | old_path.Append("/", 1); |
| 130 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | PathString new_path(path); |
| 131 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | new_path.Append("/", 1); |
| 132 | 296 | const unsigned length_after_truncate = old_path.GetLength(); | |
| 133 | |||
| 134 | 296 | catalog::DirectoryEntryList new_listing; | |
| 135 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | AppendFirstEntry(&new_listing); |
| 136 |
1/2✓ Branch 2 taken 296 times.
✗ Branch 3 not taken.
|
296 | new_catalog_mgr_->Listing(path, &new_listing); |
| 137 |
1/2✓ Branch 3 taken 296 times.
✗ Branch 4 not taken.
|
296 | sort(new_listing.begin(), new_listing.end(), IsSmaller); |
| 138 |
1/2✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
|
296 | AppendLastEntry(&new_listing); |
| 139 | |||
| 140 | 296 | unsigned i_from = 0, size_from = old_listing.size(); | |
| 141 | 296 | unsigned i_to = 0, size_to = new_listing.size(); | |
| 142 |
9/10✓ Branch 1 taken 814 times.
✓ Branch 2 taken 185 times.
✓ Branch 4 taken 814 times.
✓ Branch 5 taken 185 times.
✓ Branch 7 taken 814 times.
✓ Branch 8 taken 185 times.
✓ Branch 9 taken 999 times.
✓ Branch 10 taken 296 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 296 times.
|
2664 | while ((i_from < size_from) || (i_to < size_to)) { |
| 143 |
1/2✓ Branch 2 taken 999 times.
✗ Branch 3 not taken.
|
999 | catalog::DirectoryEntry old_entry = old_listing[i_from]; |
| 144 |
1/2✓ Branch 2 taken 999 times.
✗ Branch 3 not taken.
|
999 | catalog::DirectoryEntry new_entry = new_listing[i_to]; |
| 145 | |||
| 146 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 999 times.
|
999 | if (old_entry.linkcount() == 0) { |
| 147 | ✗ | PANIC(kLogStderr, | |
| 148 | "CatalogDiffTool - Entry %s in old catalog has linkcount 0. " | ||
| 149 | "Aborting.", | ||
| 150 | old_entry.name().c_str()); | ||
| 151 | } | ||
| 152 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 999 times.
|
999 | if (new_entry.linkcount() == 0) { |
| 153 | ✗ | PANIC(kLogStderr, | |
| 154 | "CatalogDiffTool - Entry %s in new catalog has linkcount 0. " | ||
| 155 | "Aborting.", | ||
| 156 | new_entry.name().c_str()); | ||
| 157 | } | ||
| 158 | |||
| 159 | // Skip .cvmfs hidden directory | ||
| 160 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 999 times.
|
999 | while (old_entry.IsHidden()) |
| 161 | ✗ | old_entry = old_listing[++i_from]; | |
| 162 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 999 times.
|
999 | while (new_entry.IsHidden()) |
| 163 | ✗ | new_entry = new_listing[++i_to]; | |
| 164 | |||
| 165 |
1/2✓ Branch 1 taken 999 times.
✗ Branch 2 not taken.
|
999 | old_path.Truncate(length_after_truncate); |
| 166 |
3/6✓ Branch 1 taken 999 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 999 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 999 times.
✗ Branch 10 not taken.
|
999 | old_path.Append(old_entry.name().GetChars(), old_entry.name().GetLength()); |
| 167 |
1/2✓ Branch 1 taken 999 times.
✗ Branch 2 not taken.
|
999 | new_path.Truncate(length_after_truncate); |
| 168 |
3/6✓ Branch 1 taken 999 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 999 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 999 times.
✗ Branch 10 not taken.
|
999 | new_path.Append(new_entry.name().GetChars(), new_entry.name().GetLength()); |
| 169 | |||
| 170 | 999 | XattrList xattrs; | |
| 171 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 999 times.
|
999 | if (new_entry.HasXattrs()) { |
| 172 | ✗ | new_catalog_mgr_->LookupXattrs(new_path, &xattrs); | |
| 173 | } | ||
| 174 | |||
| 175 |
3/4✓ Branch 1 taken 999 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 74 times.
✓ Branch 4 taken 925 times.
|
999 | if (IsSmaller(new_entry, old_entry)) { |
| 176 | 74 | i_to++; | |
| 177 | 74 | bool recurse = new_entry.IsDirectory(); | |
| 178 |
2/4✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 74 times.
✗ Branch 4 not taken.
|
74 | if (IsReportablePath(new_path)) { |
| 179 |
1/2✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
|
74 | FileChunkList chunks; |
| 180 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
|
74 | if (new_entry.IsChunkedFile()) { |
| 181 | ✗ | new_catalog_mgr_->ListFileChunks(new_path, new_entry.hash_algorithm(), | |
| 182 | &chunks); | ||
| 183 | } | ||
| 184 |
1/2✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
|
74 | recurse &= ReportAddition(new_path, new_entry, xattrs, chunks); |
| 185 | 74 | } | |
| 186 |
2/2✓ Branch 0 taken 37 times.
✓ Branch 1 taken 37 times.
|
74 | if (recurse) { |
| 187 |
1/2✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
|
37 | DiffRec(new_path); |
| 188 | } | ||
| 189 | 74 | continue; | |
| 190 |
3/4✓ Branch 1 taken 925 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 111 times.
✓ Branch 4 taken 814 times.
|
999 | } else if (IsSmaller(old_entry, new_entry)) { |
| 191 | 111 | i_from++; | |
| 192 |
5/6✓ Branch 1 taken 37 times.
✓ Branch 2 taken 74 times.
✓ Branch 4 taken 37 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 37 times.
✓ Branch 7 taken 74 times.
|
111 | if (old_entry.IsDirectory() && !old_entry.IsNestedCatalogMountpoint()) { |
| 193 |
1/2✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
|
37 | DiffRec(old_path); |
| 194 | } | ||
| 195 |
2/4✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 111 times.
✗ Branch 4 not taken.
|
111 | if (IsReportablePath(old_path)) { |
| 196 |
1/2✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
|
111 | ReportRemoval(old_path, old_entry); |
| 197 | } | ||
| 198 | 111 | continue; | |
| 199 | } | ||
| 200 | |||
| 201 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 814 times.
|
814 | assert(old_path == new_path); |
| 202 | 814 | i_from++; | |
| 203 | 814 | i_to++; | |
| 204 | |||
| 205 |
1/2✓ Branch 1 taken 814 times.
✗ Branch 2 not taken.
|
814 | const catalog::DirectoryEntryBase::Differences diff = old_entry.CompareTo( |
| 206 | new_entry); | ||
| 207 | 814 | if ((diff == catalog::DirectoryEntryBase::Difference::kIdentical) | |
| 208 |
4/6✓ Branch 0 taken 703 times.
✓ Branch 1 taken 111 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 703 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 814 times.
|
814 | && old_entry.IsNestedCatalogMountpoint()) { |
| 209 | // Early recursion stop if nested catalogs are identical | ||
| 210 | ✗ | shash::Any id_nested_from, id_nested_to; | |
| 211 | ✗ | id_nested_from = old_catalog_mgr_->GetNestedCatalogHash(old_path); | |
| 212 | ✗ | id_nested_to = new_catalog_mgr_->GetNestedCatalogHash(new_path); | |
| 213 | ✗ | assert(!id_nested_from.IsNull() && !id_nested_to.IsNull()); | |
| 214 | ✗ | if (id_nested_from == id_nested_to) | |
| 215 | ✗ | continue; | |
| 216 | } | ||
| 217 | |||
| 218 |
1/2✓ Branch 1 taken 814 times.
✗ Branch 2 not taken.
|
814 | if (IsReportablePath(old_path) |
| 219 |
5/6✓ Branch 0 taken 814 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 703 times.
✓ Branch 3 taken 111 times.
✓ Branch 4 taken 111 times.
✓ Branch 5 taken 703 times.
|
1517 | && ((diff != catalog::DirectoryEntryBase::Difference::kIdentical) |
| 220 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 703 times.
|
703 | || old_entry.IsNestedCatalogMountpoint())) { |
| 221 | // Modified directory entry, or nested catalog with modified hash | ||
| 222 |
1/2✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
|
111 | FileChunkList chunks; |
| 223 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 111 times.
|
111 | if (new_entry.IsChunkedFile()) { |
| 224 | ✗ | new_catalog_mgr_->ListFileChunks(new_path, new_entry.hash_algorithm(), | |
| 225 | &chunks); | ||
| 226 | } | ||
| 227 |
1/2✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
|
111 | const bool recurse = ReportModification(old_path, old_entry, new_entry, |
| 228 | xattrs, chunks); | ||
| 229 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 111 times.
|
111 | if (!recurse) |
| 230 | ✗ | continue; | |
| 231 |
1/2✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
|
111 | } |
| 232 | |||
| 233 |
5/6✓ Branch 1 taken 666 times.
✓ Branch 2 taken 148 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 666 times.
✓ Branch 6 taken 148 times.
✓ Branch 7 taken 666 times.
|
814 | if (old_entry.IsDirectory() || new_entry.IsDirectory()) { |
| 234 |
1/2✓ Branch 1 taken 148 times.
✗ Branch 2 not taken.
|
148 | DiffRec(old_path); |
| 235 | } | ||
| 236 | } | ||
| 237 | 296 | } | |
| 238 | |||
| 239 | #endif // CVMFS_CATALOG_DIFF_TOOL_IMPL_H_ | ||
| 240 |