GCC Code Coverage Report


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