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