GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/catalog_diff_tool_impl.h
Date: 2026-10-18 02:40:11
Exec Total Coverage
Lines: 115 134 85.8%
Branches: 111 210 52.9%

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 1408 inline void AppendFirstEntry(catalog::DirectoryEntryList *entry_list) {
27
1/2
✓ Branch 1 taken 1408 times.
✗ Branch 2 not taken.
1408 const catalog::DirectoryEntry empty_entry;
28
1/2
✓ Branch 1 taken 1408 times.
✗ Branch 2 not taken.
1408 entry_list->push_back(empty_entry);
29 1408 }
30
31 1408 inline void AppendLastEntry(catalog::DirectoryEntryList *entry_list) {
32
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1408 times.
1408 assert(!entry_list->empty());
33
1/2
✓ Branch 1 taken 1408 times.
✗ Branch 2 not taken.
1408 catalog::DirectoryEntry last_entry;
34 1408 last_entry.set_inode(kLastInode);
35
1/2
✓ Branch 1 taken 1408 times.
✗ Branch 2 not taken.
1408 entry_list->push_back(last_entry);
36 1408 }
37
38 6776 inline bool IsSmaller(const catalog::DirectoryEntry &a,
39 const catalog::DirectoryEntry &b) {
40 6776 const bool a_is_first = (a.inode()
41 6776 == catalog::DirectoryEntryBase::kInvalidInode);
42 6776 const bool a_is_last = (a.inode() == kLastInode);
43 6776 const bool b_is_first = (b.inode()
44 6776 == catalog::DirectoryEntryBase::kInvalidInode);
45 6776 const bool b_is_last = (b.inode() == kLastInode);
46
47
4/4
✓ Branch 0 taken 5280 times.
✓ Branch 1 taken 1496 times.
✓ Branch 2 taken 3608 times.
✓ Branch 3 taken 1672 times.
6776 if (a_is_last || b_is_first)
48 5104 return false;
49
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1672 times.
1672 if (a_is_first)
50 ✗ return !b_is_first;
51
2/2
✓ Branch 0 taken 440 times.
✓ Branch 1 taken 1232 times.
1672 if (b_is_last)
52 440 return !a_is_last;
53
1/2
✓ Branch 2 taken 1232 times.
✗ Branch 3 not taken.
1232 return a.name() < b.name();
54 }
55
56 template<typename RoCatalogMgr>
57 220 bool CatalogDiffTool<RoCatalogMgr>::Init() {
58
1/2
✓ Branch 0 taken 220 times.
✗ Branch 1 not taken.
220 if (needs_setup_) {
59 // Create a temp directory
60 220 old_raii_temp_dir_ = std::unique_ptr<RaiiTempDir>(
61 220 RaiiTempDir::Create(temp_dir_prefix_));
62 220 new_raii_temp_dir_ = std::unique_ptr<RaiiTempDir>(
63 220 RaiiTempDir::Create(temp_dir_prefix_));
64
65 // Old catalog from release manager machine (before lease)
66 440 old_catalog_mgr_ = std::unique_ptr<RoCatalogMgr>(OpenCatalogManager(
67
1/2
✓ Branch 1 taken 220 times.
✗ Branch 2 not taken.
220 repo_path_, old_raii_temp_dir_->dir(), old_root_hash_,
68 220 download_manager_, &stats_old_, cache_dir_));
69
70 // New catalog from release manager machine (before lease)
71 440 new_catalog_mgr_ = std::unique_ptr<RoCatalogMgr>(OpenCatalogManager(
72
1/2
✓ Branch 1 taken 220 times.
✗ Branch 2 not taken.
220 repo_path_, new_raii_temp_dir_->dir(), new_root_hash_,
73 220 download_manager_, &stats_new_, cache_dir_));
74
75
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 220 times.
220 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 220 times.
220 if (new_catalog_mgr_.get() == nullptr) {
81 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Could not open new catalog");
82 ✗ return false;
83 }
84 }
85
86 220 return true;
87 }
88
89 template<typename RoCatalogMgr>
90 220 bool CatalogDiffTool<RoCatalogMgr>::Run(const PathString &path) {
91 220 DiffRec(path);
92
93 220 return true;
94 }
95
96 template<typename RoCatalogMgr>
97 440 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 440 times.
✗ Branch 3 not taken.
440 RoCatalogMgr *mgr = new RoCatalogMgr(
102 root_hash, repo_path, temp_dir, download_manager, stats, true, cache_dir);
103 440 mgr->Init();
104
105 440 return mgr;
106 }
107
108 template<typename RoCatalogMgr>
109 792 void CatalogDiffTool<RoCatalogMgr>::DiffRec(const PathString &path) {
110 // Terminate recursion upon reaching an ignored path
111
3/4
✓ Branch 1 taken 792 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✓ Branch 4 taken 704 times.
792 if (IsIgnoredPath(path)) {
112
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 88 times.
88 assert(!IsReportablePath(path));
113 88 return;
114 }
115
116
2/4
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 704 times.
✗ Branch 6 not taken.
704 LogCvmfs(kLogReceiver, kLogDebug, "DiffRec: recursing into %s",
117 path.ToString().c_str());
118
119 704 catalog::DirectoryEntryList old_listing;
120
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 AppendFirstEntry(&old_listing);
121
1/2
✓ Branch 2 taken 704 times.
✗ Branch 3 not taken.
704 old_catalog_mgr_->Listing(path, &old_listing);
122
1/2
✓ Branch 3 taken 704 times.
✗ Branch 4 not taken.
704 sort(old_listing.begin(), old_listing.end(), IsSmaller);
123
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 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 704 times.
✗ Branch 2 not taken.
704 PathString old_path(path);
129
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 old_path.Append("/", 1);
130
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 PathString new_path(path);
131
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 new_path.Append("/", 1);
132 704 const unsigned length_after_truncate = old_path.GetLength();
133
134 704 catalog::DirectoryEntryList new_listing;
135
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 AppendFirstEntry(&new_listing);
136
1/2
✓ Branch 2 taken 704 times.
✗ Branch 3 not taken.
704 new_catalog_mgr_->Listing(path, &new_listing);
137
1/2
✓ Branch 3 taken 704 times.
✗ Branch 4 not taken.
704 sort(new_listing.begin(), new_listing.end(), IsSmaller);
138
1/2
✓ Branch 1 taken 704 times.
✗ Branch 2 not taken.
704 AppendLastEntry(&new_listing);
139
140 704 unsigned i_from = 0, size_from = old_listing.size();
141 704 unsigned i_to = 0, size_to = new_listing.size();
142
9/10
✓ Branch 1 taken 1804 times.
✓ Branch 2 taken 484 times.
✓ Branch 4 taken 1804 times.
✓ Branch 5 taken 484 times.
✓ Branch 7 taken 1804 times.
✓ Branch 8 taken 484 times.
✓ Branch 9 taken 2288 times.
✓ Branch 10 taken 704 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 704 times.
6248 while ((i_from < size_from) || (i_to < size_to)) {
143
1/2
✓ Branch 2 taken 2288 times.
✗ Branch 3 not taken.
2288 catalog::DirectoryEntry old_entry = old_listing[i_from];
144
1/2
✓ Branch 2 taken 2288 times.
✗ Branch 3 not taken.
2288 catalog::DirectoryEntry new_entry = new_listing[i_to];
145
146
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2288 times.
2288 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 2288 times.
2288 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 2288 times.
2288 while (old_entry.IsHidden())
161 ✗ old_entry = old_listing[++i_from];
162
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2288 times.
2288 while (new_entry.IsHidden())
163 ✗ new_entry = new_listing[++i_to];
164
165
1/2
✓ Branch 1 taken 2288 times.
✗ Branch 2 not taken.
2288 old_path.Truncate(length_after_truncate);
166
3/6
✓ Branch 1 taken 2288 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 2288 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 2288 times.
✗ Branch 10 not taken.
2288 old_path.Append(old_entry.name().GetChars(), old_entry.name().GetLength());
167
1/2
✓ Branch 1 taken 2288 times.
✗ Branch 2 not taken.
2288 new_path.Truncate(length_after_truncate);
168
3/6
✓ Branch 1 taken 2288 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 2288 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 2288 times.
✗ Branch 10 not taken.
2288 new_path.Append(new_entry.name().GetChars(), new_entry.name().GetLength());
169
170 2288 XattrList xattrs;
171
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2288 times.
2288 if (new_entry.HasXattrs()) {
172 ✗ new_catalog_mgr_->LookupXattrs(new_path, &xattrs);
173 }
174
175
3/4
✓ Branch 1 taken 2288 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 352 times.
✓ Branch 4 taken 1936 times.
2288 if (IsSmaller(new_entry, old_entry)) {
176 352 i_to++;
177 352 bool recurse = new_entry.IsDirectory();
178
3/4
✓ Branch 1 taken 352 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 264 times.
✓ Branch 4 taken 88 times.
352 if (IsReportablePath(new_path)) {
179
1/2
✓ Branch 1 taken 264 times.
✗ Branch 2 not taken.
264 FileChunkList chunks;
180
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 264 times.
264 if (new_entry.IsChunkedFile()) {
181 ✗ new_catalog_mgr_->ListFileChunks(new_path, new_entry.hash_algorithm(),
182 &chunks);
183 }
184
1/2
✓ Branch 1 taken 264 times.
✗ Branch 2 not taken.
264 recurse &= ReportAddition(new_path, new_entry, xattrs, chunks);
185 264 }
186
2/2
✓ Branch 0 taken 220 times.
✓ Branch 1 taken 132 times.
352 if (recurse) {
187
1/2
✓ Branch 1 taken 220 times.
✗ Branch 2 not taken.
220 DiffRec(new_path);
188 }
189 352 continue;
190
3/4
✓ Branch 1 taken 1936 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 132 times.
✓ Branch 4 taken 1804 times.
2288 } else if (IsSmaller(old_entry, new_entry)) {
191 132 i_from++;
192
5/6
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 88 times.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 44 times.
✓ Branch 7 taken 88 times.
132 if (old_entry.IsDirectory() && !old_entry.IsNestedCatalogMountpoint()) {
193
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 DiffRec(old_path);
194 }
195
2/4
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 132 times.
✗ Branch 4 not taken.
132 if (IsReportablePath(old_path)) {
196
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 ReportRemoval(old_path, old_entry);
197 }
198 132 continue;
199 }
200
201
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1804 times.
1804 assert(old_path == new_path);
202 1804 i_from++;
203 1804 i_to++;
204
205
1/2
✓ Branch 1 taken 1804 times.
✗ Branch 2 not taken.
1804 const catalog::DirectoryEntryBase::Differences diff = old_entry.CompareTo(
206 new_entry);
207 1804 if ((diff == catalog::DirectoryEntryBase::Difference::kIdentical)
208
4/6
✓ Branch 0 taken 1672 times.
✓ Branch 1 taken 132 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1672 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1804 times.
1804 && 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 1804 times.
✗ Branch 2 not taken.
1804 if (IsReportablePath(old_path)
219
6/6
✓ Branch 0 taken 1364 times.
✓ Branch 1 taken 440 times.
✓ Branch 2 taken 1232 times.
✓ Branch 3 taken 132 times.
✓ Branch 4 taken 132 times.
✓ Branch 5 taken 1672 times.
3036 && ((diff != catalog::DirectoryEntryBase::Difference::kIdentical)
220
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1232 times.
1232 || old_entry.IsNestedCatalogMountpoint())) {
221 // Modified directory entry, or nested catalog with modified hash
222
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 FileChunkList chunks;
223
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 132 times.
132 if (new_entry.IsChunkedFile()) {
224 ✗ new_catalog_mgr_->ListFileChunks(new_path, new_entry.hash_algorithm(),
225 &chunks);
226 }
227
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 const bool recurse = ReportModification(old_path, old_entry, new_entry,
228 xattrs, chunks);
229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 132 times.
132 if (!recurse)
230 ✗ continue;
231
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 }
232
233
5/6
✓ Branch 1 taken 1496 times.
✓ Branch 2 taken 308 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1496 times.
✓ Branch 6 taken 308 times.
✓ Branch 7 taken 1496 times.
1804 if (old_entry.IsDirectory() || new_entry.IsDirectory()) {
234
1/2
✓ Branch 1 taken 308 times.
✗ Branch 2 not taken.
308 DiffRec(old_path);
235 }
236 }
237 704 }
238
239 #endif // CVMFS_CATALOG_DIFF_TOOL_IMPL_H_
240