GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/catalog_virtual.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 13 194 6.7%
Branches: 9 425 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
11 #include "catalog_mgr_rw.h"
12 #include "compression/compression.h"
13 #include "history.h"
14 #include "swissknife_history.h"
15 #include "swissknife_sync.h"
16 #include "util/logging.h"
17 #include "util/pointer.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 =
74 catalog_mgr_->spooler_->GetHashAlgorithm();
75 shash::Any file_hash(algorithm);
76 void *empty_compressed;
77 uint64_t sz_empty_compressed;
78 const bool retval =
79 zlib::CompressMem2Mem(NULL, 0, &empty_compressed, &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 =
115 catalog_mgr_->LookupPath("/" + string(kVirtualPath), 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 14 bool VirtualCatalog::ParseActions(const string &action_desc, int *actions) {
194 14 *actions = kActionNone;
195
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
14 if (action_desc.empty())
196 2 return true;
197
198
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 vector<string> action_tokens = SplitString(action_desc, ',');
199
2/2
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 8 times.
28 for (unsigned i = 0; i < action_tokens.size(); ++i) {
200
2/2
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 12 times.
20 if (action_tokens[i] == "snapshots") {
201 8 *actions |= kActionGenerateSnapshots;
202
2/2
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 4 times.
12 } else if (action_tokens[i] == "remove") {
203 8 *actions |= kActionRemove;
204 } else {
205 4 return false;
206 }
207 }
208 8 return true;
209 12 }
210
211
212 void VirtualCatalog::GetSortedTagsFromHistory(vector<TagId> *tags) {
213 const UniquePtr<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 UniquePtr<Catalog> catalog(
247 assistant_.GetCatalog(tag.hash, swissknife::Assistant::kOpenReadOnly));
248 assert(catalog.IsValid());
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 =
300 catalog_mgr_->Listing(PathString("/" + directory), &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 =
323 string(kVirtualPath) + "/" + string(kSnapshotDirectory) + "/" + 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