GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/catalog_virtual.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 13 196 6.6%
Branches: 9 421 2.1%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "catalog_virtual.h"
6
7 #include <algorithm>
8 #include <cassert>
9 #include <cstdlib>
10 #include <memory>
11
12 #include "catalog_mgr_rw.h"
13 #include "compression/compression.h"
14 #include "history.h"
15 #include "swissknife_history.h"
16 #include "swissknife_sync.h"
17 #include "util/logging.h"
18 #include "util/posix.h"
19 #include "util/string.h"
20 #include "xattr.h"
21
22 using namespace std; // NOLINT
23
24 namespace catalog {
25
26 const char *VirtualCatalog::kVirtualPath = ".cvmfs";
27 const char *VirtualCatalog::kSnapshotDirectory = "snapshots";
28 const int VirtualCatalog::kActionNone = 0x00;
29 const int VirtualCatalog::kActionGenerateSnapshots = 0x01;
30 const int VirtualCatalog::kActionRemove = 0x02;
31
32
33 ✗ void VirtualCatalog::CreateBaseDirectory() {
34 // Add /.cvmfs as a nested catalog
35 ✗ DirectoryEntryBase entry_dir;
36 ✗ entry_dir.name_ = NameString(string(kVirtualPath));
37 ✗ entry_dir.mode_ = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH
38 | S_IXOTH;
39 ✗ entry_dir.uid_ = 0;
40 ✗ entry_dir.gid_ = 0;
41 ✗ entry_dir.size_ = 97;
42 ✗ entry_dir.mtime_ = time(NULL);
43 ✗ catalog_mgr_->AddDirectory(entry_dir, XattrList(), "");
44 ✗ WritableCatalog *parent_catalog = catalog_mgr_->GetHostingCatalog(
45 kVirtualPath);
46 ✗ catalog_mgr_->CreateNestedCatalog(kVirtualPath);
47 ✗ WritableCatalog *virtual_catalog = catalog_mgr_->GetHostingCatalog(
48 kVirtualPath);
49 ✗ assert(parent_catalog != virtual_catalog);
50
51 // Set hidden flag in parent catalog
52 ✗ DirectoryEntry entry_parent;
53 ✗ bool retval = parent_catalog->LookupPath(
54 ✗ PathString("/" + string(kVirtualPath)), &entry_parent);
55 ✗ assert(retval);
56 ✗ entry_parent.set_is_hidden(true);
57 ✗ parent_catalog->UpdateEntry(entry_parent, "/" + string(kVirtualPath));
58
59 // Set hidden flag in nested catalog
60 ✗ DirectoryEntry entry_virtual;
61 ✗ retval = virtual_catalog->LookupPath(PathString("/" + string(kVirtualPath)),
62 &entry_virtual);
63 ✗ assert(retval);
64 ✗ entry_virtual.set_is_hidden(true);
65 ✗ virtual_catalog->UpdateEntry(entry_virtual, "/" + string(kVirtualPath));
66 }
67
68
69 ✗ void VirtualCatalog::CreateNestedCatalogMarker() {
70 ✗ DirectoryEntryBase entry_marker;
71 // Note that another entity needs to ensure that the object of an empty
72 // file is in the repository! It is currently done by the sync_mediator.
73 ✗ const shash::Algorithms algorithm = catalog_mgr_->spooler_
74 ✗ ->GetHashAlgorithm();
75 ✗ shash::Any file_hash(algorithm);
76 void *empty_compressed;
77 uint64_t sz_empty_compressed;
78 ✗ const bool retval = zlib::CompressMem2Mem(NULL, 0, &empty_compressed,
79 &sz_empty_compressed);
80 ✗ assert(retval);
81 ✗ shash::HashMem(static_cast<unsigned char *>(empty_compressed),
82 sz_empty_compressed, &file_hash);
83 ✗ free(empty_compressed);
84 ✗ entry_marker.name_ = NameString(".cvmfscatalog");
85 ✗ entry_marker.mode_ = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
86 ✗ entry_marker.checksum_ = file_hash;
87 ✗ entry_marker.mtime_ = time(NULL);
88 ✗ entry_marker.uid_ = 0;
89 ✗ entry_marker.gid_ = 0;
90 ✗ const XattrList xattrs;
91 ✗ catalog_mgr_->AddFile(entry_marker, xattrs, kVirtualPath);
92 }
93
94
95 ✗ void VirtualCatalog::CreateSnapshotDirectory() {
96 ✗ DirectoryEntryBase entry_dir;
97 ✗ entry_dir.name_ = NameString(string(kSnapshotDirectory));
98 ✗ entry_dir.mode_ = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH
99 | S_IXOTH;
100 ✗ entry_dir.uid_ = 0;
101 ✗ entry_dir.gid_ = 0;
102 ✗ entry_dir.size_ = 97;
103 ✗ entry_dir.mtime_ = time(NULL);
104 ✗ catalog_mgr_->AddDirectory(entry_dir, XattrList(), kVirtualPath);
105 }
106
107
108 /**
109 * Checks for the top-level /.cvmfs directory and creates it as a nested catalog
110 * if necessary.
111 */
112 ✗ void VirtualCatalog::EnsurePresence() {
113 ✗ DirectoryEntry e;
114 ✗ const bool retval = catalog_mgr_->LookupPath("/" + string(kVirtualPath),
115 kLookupDefault, &e);
116 ✗ if (!retval) {
117 ✗ LogCvmfs(kLogCatalog, kLogDebug, "creating new virtual catalog");
118 ✗ CreateBaseDirectory();
119 ✗ CreateNestedCatalogMarker();
120 ✗ CreateSnapshotDirectory();
121 }
122 ✗ assert(catalog_mgr_->IsTransitionPoint(kVirtualPath));
123 }
124
125
126 ✗ void VirtualCatalog::Generate(int actions) {
127 ✗ if (actions & kActionGenerateSnapshots) {
128 ✗ GenerateSnapshots();
129 }
130 ✗ if (actions & kActionRemove) {
131 ✗ Remove();
132 }
133 }
134
135
136 ✗ void VirtualCatalog::GenerateSnapshots() {
137 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "Creating virtual snapshots");
138 ✗ EnsurePresence();
139
140 ✗ vector<TagId> tags_history;
141 ✗ vector<TagId> tags_catalog;
142 ✗ GetSortedTagsFromHistory(&tags_history);
143 ✗ GetSortedTagsFromCatalog(&tags_catalog);
144 // Add artificial end markers to both lists
145 ✗ string tag_name_end = "";
146 ✗ if (!tags_history.empty())
147 ✗ tag_name_end = std::max(tag_name_end, tags_history.rbegin()->name);
148 ✗ if (!tags_catalog.empty())
149 ✗ tag_name_end = std::max(tag_name_end, tags_catalog.rbegin()->name);
150 ✗ tag_name_end += "X";
151 ✗ tags_history.push_back(TagId(tag_name_end, shash::Any()));
152 ✗ tags_catalog.push_back(TagId(tag_name_end, shash::Any()));
153
154 // Walk through both sorted lists concurrently and determine change set
155 ✗ unsigned i_history = 0, i_catalog = 0;
156 ✗ const unsigned last_history = tags_history.size() - 1;
157 ✗ const unsigned last_catalog = tags_catalog.size() - 1;
158 ✗ while ((i_history < last_history) || (i_catalog < last_catalog)) {
159 ✗ const TagId t_history = tags_history[i_history];
160 ✗ const TagId t_catalog = tags_catalog[i_catalog];
161
162 // Both the same, nothing to do
163 ✗ if (t_history == t_catalog) {
164 ✗ i_history++;
165 ✗ i_catalog++;
166 ✗ continue;
167 }
168
169 // Same tag name for different hash, re-insert
170 ✗ if (t_history.name == t_catalog.name) {
171 ✗ RemoveSnapshot(t_catalog);
172 ✗ InsertSnapshot(t_history);
173 ✗ i_history++;
174 ✗ i_catalog++;
175 ✗ continue;
176 }
177
178 // New tag that's missing
179 ✗ if (t_history.name < t_catalog.name) {
180 ✗ InsertSnapshot(t_history);
181 ✗ i_history++;
182 ✗ continue;
183 }
184
185 // A tag was removed but it is still present in the catalog
186 ✗ assert(t_history.name > t_catalog.name);
187 ✗ RemoveSnapshot(t_catalog);
188 ✗ i_catalog++;
189 }
190 }
191
192
193 28 bool VirtualCatalog::ParseActions(const string &action_desc, int *actions) {
194 28 *actions = kActionNone;
195
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 24 times.
28 if (action_desc.empty())
196 4 return true;
197
198
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 vector<string> action_tokens = SplitString(action_desc, ',');
199
2/2
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 16 times.
56 for (unsigned i = 0; i < action_tokens.size(); ++i) {
200
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 24 times.
40 if (action_tokens[i] == "snapshots") {
201 16 *actions |= kActionGenerateSnapshots;
202
2/2
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 8 times.
24 } else if (action_tokens[i] == "remove") {
203 16 *actions |= kActionRemove;
204 } else {
205 8 return false;
206 }
207 }
208 16 return true;
209 24 }
210
211
212 ✗ void VirtualCatalog::GetSortedTagsFromHistory(vector<TagId> *tags) {
213 const std::unique_ptr<history::History> history(
214 ✗ assistant_.GetHistory(swissknife::Assistant::kOpenReadOnly));
215 ✗ vector<history::History::Tag> tags_history;
216 ✗ const bool retval = history->List(&tags_history);
217 ✗ assert(retval);
218 ✗ for (unsigned i = 0, l = tags_history.size(); i < l; ++i) {
219 ✗ if ((tags_history[i].name == swissknife::CommandTag::kHeadTag)
220 ✗ || (tags_history[i].name == swissknife::CommandTag::kPreviousHeadTag)) {
221 ✗ continue;
222 }
223 ✗ tags->push_back(TagId(tags_history[i].name, tags_history[i].root_hash));
224 }
225 ✗ std::sort(tags->begin(), tags->end());
226 }
227
228
229 ✗ void VirtualCatalog::GetSortedTagsFromCatalog(vector<TagId> *tags) {
230 ✗ WritableCatalog *virtual_catalog = catalog_mgr_->GetHostingCatalog(
231 kVirtualPath);
232 ✗ assert(virtual_catalog != NULL);
233 Catalog::NestedCatalogList nested_catalogs = virtual_catalog
234 ✗ ->ListNestedCatalogs();
235 ✗ for (unsigned i = 0, l = nested_catalogs.size(); i < l; ++i) {
236 ✗ tags->push_back(TagId(GetFileName(nested_catalogs[i].mountpoint).ToString(),
237 ✗ nested_catalogs[i].hash));
238 }
239 ✗ std::sort(tags->begin(), tags->end());
240 }
241
242
243 ✗ void VirtualCatalog::InsertSnapshot(TagId tag) {
244 ✗ LogCvmfs(kLogCatalog, kLogDebug, "add snapshot %s (%s) to virtual catalog",
245 ✗ tag.name.c_str(), tag.hash.ToString().c_str());
246 const std::unique_ptr<Catalog> catalog(
247 ✗ assistant_.GetCatalog(tag.hash, swissknife::Assistant::kOpenReadOnly));
248 ✗ assert(catalog.get() != nullptr);
249 ✗ assert(catalog->root_prefix().IsEmpty());
250 ✗ DirectoryEntry entry_root;
251 ✗ const bool retval = catalog->LookupPath(PathString(""), &entry_root);
252 ✗ assert(retval);
253
254 // Add directory entry
255 ✗ DirectoryEntryBase entry_dir = entry_root;
256 ✗ entry_dir.name_ = NameString(tag.name);
257 ✗ catalog_mgr_->AddDirectory(
258 ✗ entry_dir, XattrList(),
259 ✗ string(kVirtualPath) + "/" + string(kSnapshotDirectory));
260
261 // Set "bind mount" flag
262 ✗ WritableCatalog *virtual_catalog = catalog_mgr_->GetHostingCatalog(
263 kVirtualPath);
264 ✗ assert(virtual_catalog != NULL);
265 ✗ const string mountpoint = "/" + string(kVirtualPath) + "/"
266 ✗ + string(kSnapshotDirectory) + "/" + tag.name;
267 ✗ DirectoryEntry entry_bind_mountpoint(entry_dir);
268 ✗ entry_bind_mountpoint.set_is_bind_mountpoint(true);
269 ✗ virtual_catalog->UpdateEntry(entry_bind_mountpoint, mountpoint);
270
271 // Register nested catalog
272 ✗ const uint64_t catalog_size = GetFileSize(catalog->database_path());
273 ✗ assert(catalog_size > 0);
274 ✗ virtual_catalog->InsertBindMountpoint(mountpoint, tag.hash, catalog_size);
275 }
276
277
278 ✗ void VirtualCatalog::Remove() {
279 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "Removing .cvmfs virtual catalog");
280
281 // Safety check, make sure we don't remove the entire repository
282 ✗ WritableCatalog *virtual_catalog = catalog_mgr_->GetHostingCatalog(
283 kVirtualPath);
284 ✗ assert(!virtual_catalog->IsRoot());
285 ✗ DirectoryEntry entry_virtual;
286 ✗ const bool retval = catalog_mgr_->LookupPath(
287 ✗ PathString("/" + string(kVirtualPath)), kLookupDefault, &entry_virtual);
288 ✗ assert(retval);
289 ✗ assert(entry_virtual.IsHidden());
290
291 ✗ RemoveRecursively(kVirtualPath);
292 ✗ catalog_mgr_->RemoveNestedCatalog(kVirtualPath);
293 ✗ catalog_mgr_->RemoveDirectory(kVirtualPath);
294 }
295
296
297 ✗ void VirtualCatalog::RemoveRecursively(const string &directory) {
298 ✗ DirectoryEntryList listing;
299 ✗ const bool retval = catalog_mgr_->Listing(PathString("/" + directory),
300 &listing);
301 ✗ assert(retval);
302 ✗ for (unsigned i = 0; i < listing.size(); ++i) {
303 ✗ const string this_path = directory + "/" + listing[i].name().ToString();
304 ✗ if (listing[i].IsDirectory()) {
305 ✗ if (!listing[i].IsBindMountpoint())
306 ✗ RemoveRecursively(this_path);
307 ✗ catalog_mgr_->RemoveDirectory(this_path);
308 ✗ } else if (listing[i].IsRegular()) {
309 ✗ assert(listing[i].name().ToString() == ".cvmfscatalog");
310 ✗ catalog_mgr_->RemoveFile(this_path);
311 } else {
312 ✗ abort();
313 }
314 }
315 }
316
317
318 ✗ void VirtualCatalog::RemoveSnapshot(TagId tag) {
319 ✗ LogCvmfs(kLogCatalog, kLogDebug,
320 "remove snapshot %s (%s) from virtual catalog", tag.name.c_str(),
321 ✗ tag.hash.ToString().c_str());
322 ✗ const string tag_dir = string(kVirtualPath) + "/" + string(kSnapshotDirectory)
323 ✗ + "/" + tag.name;
324 ✗ catalog_mgr_->RemoveDirectory(tag_dir);
325
326 ✗ WritableCatalog *virtual_catalog = catalog_mgr_->GetHostingCatalog(
327 kVirtualPath);
328 ✗ assert(virtual_catalog != NULL);
329 ✗ virtual_catalog->RemoveBindMountpoint("/" + tag_dir);
330 }
331
332
333 ✗ VirtualCatalog::VirtualCatalog(manifest::Manifest *m,
334 download::DownloadManager *d,
335 catalog::WritableCatalogManager *c,
336 ✗ SyncParameters *p)
337 ✗ : catalog_mgr_(c), assistant_(d, m, p->stratum0, p->dir_temp) { }
338
339 } // namespace catalog
340