GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/sync_item.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 58 265 21.9%
Branches: 29 364 8.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM file system
3 */
4
5 #include "sync_item.h"
6
7
8 #if !defined(__APPLE__)
9 #include <sys/sysmacros.h>
10 #endif // __APPLE__
11
12 #include <cerrno>
13 #include <vector>
14
15 #include "ingestion/ingestion_source.h"
16 #include "sync_union.h"
17 #include "util/string.h"
18 #include "util/exception.h"
19
20 using namespace std; // NOLINT
21
22 namespace publish {
23
24 SyncItem::SyncItem()
25 : rdonly_type_(static_cast<SyncItemType>(0))
26 , graft_size_(-1)
27 , scratch_type_(static_cast<SyncItemType>(0))
28 , union_engine_(NULL)
29 , whiteout_(false)
30 , opaque_(false)
31 , masked_hardlink_(false)
32 , has_catalog_marker_(false)
33 , valid_graft_(false)
34 , graft_marker_present_(false)
35 , external_data_(false)
36 , direct_io_(false)
37 , graft_chunklist_(NULL)
38 , compression_algorithm_(zlib::kZlibDefault)
39 , has_compression_algorithm_(false) { }
40
41 1058 SyncItem::SyncItem(const std::string &relative_parent_path,
42 const std::string &filename,
43 const SyncUnion *union_engine,
44 1058 const SyncItemType entry_type)
45 1058 : rdonly_type_(kItemUnknown)
46 1058 , graft_size_(-1)
47 1058 , scratch_type_(entry_type)
48 1058 , filename_(filename)
49 1058 , union_engine_(union_engine)
50 1058 , whiteout_(false)
51 1058 , opaque_(false)
52 1058 , masked_hardlink_(false)
53 1058 , has_catalog_marker_(false)
54 1058 , valid_graft_(false)
55 1058 , graft_marker_present_(false)
56 1058 , external_data_(false)
57 1058 , direct_io_(false)
58
1/2
✓ Branch 1 taken 1058 times.
✗ Branch 2 not taken.
1058 , relative_parent_path_(relative_parent_path)
59 1058 , graft_chunklist_(NULL)
60 1058 , compression_algorithm_(zlib::kZlibDefault)
61 2116 , has_compression_algorithm_(false) {
62 1058 content_hash_.algorithm = shash::kAny;
63 1058 }
64
65
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1058 times.
2116 SyncItem::~SyncItem() { delete graft_chunklist_; }
66
67
68 SyncItemType SyncItem::GetGenericFiletype(
69 const SyncItem::EntryStat &stat) const {
70 const SyncItemType type = stat.GetSyncItemType();
71 if (type == kItemUnknown) {
72 PANIC(kLogStderr,
73 "[WARNING] '%s' has an unsupported file type (st_mode: %d errno: %d)",
74 GetRelativePath().c_str(), stat.stat.st_mode, stat.error_code);
75 }
76 return type;
77 }
78
79
80 782 SyncItemType SyncItem::GetRdOnlyFiletype() const {
81 782 StatRdOnly();
82 // file could not exist in read-only branch, or a regular file could have
83 // been replaced by a directory in the read/write branch, like:
84 // rdonly:
85 // /foo/bar/regular_file <-- ENOTDIR when asking for (.../is_dir_now)
86 // r/w:
87 // /foo/bar/regular_file/
88 // /foo/bar/regular_file/is_dir_now
89
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 782 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
782 if (rdonly_stat_.error_code == ENOENT || rdonly_stat_.error_code == ENOTDIR)
90 782 return kItemNew;
91 return GetGenericFiletype(rdonly_stat_);
92 }
93
94
95 SyncItemType SyncItemNative::GetScratchFiletype() const {
96 StatScratch(/* refresh= */ false);
97 if (scratch_stat_.error_code != 0) {
98 PANIC(kLogStderr, "[WARNING] Failed to stat() '%s' in scratch. (errno: %s)",
99 GetRelativePath().c_str(), scratch_stat_.error_code);
100 }
101
102 return GetGenericFiletype(scratch_stat_);
103 }
104
105 SyncItemType SyncItem::GetUnionFiletype() const {
106 StatUnion();
107 if (union_stat_.error_code == ENOENT || union_stat_.error_code == ENOTDIR)
108 return kItemUnknown;
109 return GetGenericFiletype(union_stat_);
110 }
111
112 1012 bool SyncItemNative::IsType(const SyncItemType expected_type) const {
113
2/6
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 1012 times.
1012 if (filename().substr(0, 12) == ".cvmfsgraft-") {
114 scratch_type_ = kItemMarker;
115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1012 times.
1012 } else if (scratch_type_ == kItemUnknown) {
116 scratch_type_ = GetScratchFiletype();
117 }
118 1012 return scratch_type_ == expected_type;
119 }
120
121 void SyncItem::MarkAsWhiteout(const std::string &actual_filename) {
122 StatScratch(/* refresh= */ true);
123 // Mark the file as whiteout entry and strip the whiteout prefix
124 whiteout_ = true;
125 filename_ = actual_filename;
126
127 // Find the entry in the repository
128 StatRdOnly(true); // <== refreshing the stat (filename might have changed)
129
130 const SyncItemType deleted_type = (rdonly_stat_.error_code == 0)
131 ? GetRdOnlyFiletype()
132 : kItemUnknown;
133
134 rdonly_type_ = deleted_type;
135 scratch_type_ = deleted_type;
136
137 if (deleted_type == kItemUnknown) {
138 // Marking a SyncItem as 'whiteout' but no file to be removed found: This
139 // should not happen (actually AUFS prevents users from creating whiteouts)
140 // but can be provoked through an AUFS 'bug' (see test 593 or CVM-880).
141 // --> Warn the user, continue with kItemUnknown and cross your fingers!
142 PrintWarning("'" + GetRelativePath()
143 + "' should be deleted, but was not found in repository.");
144 }
145 }
146
147
148 void SyncItem::MarkAsOpaqueDirectory() {
149 assert(IsDirectory());
150 opaque_ = true;
151 }
152
153
154 unsigned int SyncItem::GetRdOnlyLinkcount() const {
155 StatRdOnly();
156 return rdonly_stat_.stat.st_nlink;
157 }
158
159
160 uint64_t SyncItem::GetRdOnlyInode() const {
161 StatRdOnly();
162 return rdonly_stat_.stat.st_ino;
163 }
164
165
166 unsigned int SyncItem::GetUnionLinkcount() const {
167 StatUnion();
168 return union_stat_.stat.st_nlink;
169 }
170
171
172 uint64_t SyncItem::GetUnionInode() const {
173 StatUnion();
174 return union_stat_.stat.st_ino;
175 }
176
177 uint64_t SyncItem::GetScratchSize() const {
178 StatScratch(/* refresh= */ false);
179 return scratch_stat_.stat.st_size;
180 }
181
182 uint64_t SyncItem::GetRdOnlySize() const {
183 StatRdOnly();
184 return rdonly_stat_.stat.st_size;
185 }
186
187 IngestionSource *SyncItemNative::CreateIngestionSource() const {
188 return new FileIngestionSource(GetUnionPath());
189 }
190
191 1196 void SyncItem::StatGeneric(const string &path,
192 EntryStat *info,
193 const bool refresh) {
194
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1196 if (info->obtained && !refresh)
195 return;
196 1196 const int retval = platform_lstat(path.c_str(), &info->stat);
197
1/2
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
1196 info->error_code = (retval != 0) ? errno : 0;
198 1196 info->obtained = true;
199 }
200
201
202 catalog::DirectoryEntryBase SyncItemNative::CreateBasicCatalogDirent(
203 bool enable_mtime_ns) const {
204 catalog::DirectoryEntryBase dirent;
205
206 // inode and parent inode is determined at runtime of client
207 dirent.inode_ = catalog::DirectoryEntry::kInvalidInode;
208
209 // this might mask the actual link count in case hardlinks are not supported
210 // (i.e. on setups using OverlayFS)
211 dirent.linkcount_ = HasHardlinks() ? this->GetUnionStat().st_nlink : 1;
212
213 dirent.mode_ = this->GetUnionStat().st_mode;
214 dirent.uid_ = this->GetUnionStat().st_uid;
215 dirent.gid_ = this->GetUnionStat().st_gid;
216 dirent.size_ = graft_size_ > -1 ? graft_size_ : this->GetUnionStat().st_size;
217 dirent.mtime_ = this->GetUnionStat().st_mtime;
218 dirent.checksum_ = this->GetContentHash();
219 dirent.is_external_file_ = this->IsExternalData();
220 dirent.is_direct_io_ = this->IsDirectIo();
221 dirent.compression_algorithm_ = this->GetCompressionAlgorithm();
222
223 dirent.name_.Assign(filename().data(), filename().length());
224
225 if (this->IsSymlink()) {
226 char slnk[PATH_MAX + 1];
227 const ssize_t length = readlink((this->GetUnionPath()).c_str(), slnk,
228 PATH_MAX);
229 assert(length >= 0);
230 dirent.symlink_.Assign(slnk, length);
231 }
232
233 if (this->IsCharacterDevice() || this->IsBlockDevice()) {
234 dirent.size_ = makedev(GetRdevMajor(), GetRdevMinor());
235 }
236
237 if (enable_mtime_ns) {
238 #ifdef __APPLE__
239 dirent.mtime_ns_ = static_cast<int32_t>(
240 this->GetUnionStat().st_mtimespec.tv_nsec);
241 #else
242 dirent.mtime_ns_ = static_cast<int32_t>(
243 this->GetUnionStat().st_mtim.tv_nsec);
244 #endif
245 }
246
247 return dirent;
248 }
249
250
251 782 std::string SyncItem::GetRdOnlyPath() const {
252
1/2
✓ Branch 1 taken 782 times.
✗ Branch 2 not taken.
782 const string relative_path = GetRelativePath().empty()
253 ? ""
254
5/18
✗ Branch 0 not taken.
✓ Branch 1 taken 782 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 782 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 782 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 782 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 782 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
1564 : "/" + GetRelativePath();
255
2/4
✓ Branch 1 taken 782 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 782 times.
✗ Branch 5 not taken.
2346 return union_engine_->rdonly_path() + relative_path;
256 782 }
257
258 828 std::string SyncItem::GetUnionPath() const {
259
1/2
✓ Branch 1 taken 828 times.
✗ Branch 2 not taken.
828 const string relative_path = GetRelativePath().empty()
260 ? ""
261
5/18
✗ Branch 0 not taken.
✓ Branch 1 taken 828 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 828 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 828 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 828 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 828 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
1656 : "/" + GetRelativePath();
262
2/4
✓ Branch 1 taken 828 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 828 times.
✗ Branch 5 not taken.
2484 return union_engine_->union_path() + relative_path;
263 828 }
264
265 std::string SyncItem::GetScratchPath() const {
266 const string relative_path = GetRelativePath().empty()
267 ? ""
268 : "/" + GetRelativePath();
269 return union_engine_->scratch_path() + relative_path;
270 // return union_engine_->scratch_path() + filename();
271 }
272
273 414 void SyncItem::CheckMarkerFiles() {
274
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 414 times.
414 if (IsRegularFile()) {
275 CheckGraft();
276
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 } else if (IsDirectory()) {
277 414 CheckCatalogMarker();
278 }
279 414 }
280
281 414 void SyncItem::CheckCatalogMarker() {
282
2/4
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 414 times.
✗ Branch 5 not taken.
414 const std::string path(GetUnionPath() + "/.cvmfscatalog");
283 414 EntryStat stat;
284 414 StatGeneric(path, &stat, false);
285
1/2
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
414 if (stat.error_code) {
286 414 has_catalog_marker_ = false;
287 414 return;
288 }
289 if (stat.GetSyncItemType() == kItemFile) {
290 has_catalog_marker_ = true;
291 return;
292 }
293 PANIC(kLogStderr, "Error: '%s' is not a regular file.", path.c_str());
294 414 }
295
296
297 std::string SyncItem::GetGraftMarkerPath() const {
298 return union_engine_->scratch_path() + "/"
299 + ((relative_parent_path_.empty())
300 ? ".cvmfsgraft-" + filename_
301 : relative_parent_path_
302 + (filename_.empty() ? ""
303 : ("/.cvmfsgraft-" + filename_)));
304 }
305
306 void SyncItem::CheckGraft() {
307 valid_graft_ = false;
308 bool found_checksum = false;
309 const std::string checksum_type;
310 const std::string checksum_value;
311 const std::string graftfile = GetGraftMarkerPath();
312 LogCvmfs(kLogFsTraversal, kLogDebug, "Checking potential graft path %s.",
313 graftfile.c_str());
314 FILE *fp = fopen(graftfile.c_str(), "r");
315 if (fp == NULL) {
316 // This sync item can be a file from a removed directory tree on overlayfs.
317 // In this case, the entire tree is missing on the scratch directory and
318 // the errno is ENOTDIR.
319 if ((errno != ENOENT) && (errno != ENOTDIR)) {
320 LogCvmfs(kLogFsTraversal, kLogWarning,
321 "Unable to open graft file "
322 "(%s): %s (errno=%d)",
323 graftfile.c_str(), strerror(errno), errno);
324 }
325 return;
326 }
327 graft_marker_present_ = true;
328 valid_graft_ = true;
329 std::string line;
330 const std::vector<std::string> contents;
331
332 std::vector<off_t> chunk_offsets;
333 std::vector<shash::Any> chunk_checksums;
334
335 while (GetLineFile(fp, &line)) {
336 std::string trimmed_line = Trim(line);
337
338 if (!trimmed_line.size()) {
339 continue;
340 }
341 if (trimmed_line[0] == '#') {
342 continue;
343 }
344
345 std::vector<std::string> info = SplitStringBounded(2, trimmed_line, '=');
346
347 if (info.size() != 2) {
348 LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid line in graft file: %s",
349 trimmed_line.c_str());
350 }
351 info[0] = Trim(info[0]);
352 info[1] = Trim(info[1]);
353 if (info[0] == "size") {
354 uint64_t tmp_size;
355 if (!String2Uint64Parse(info[1], &tmp_size)) {
356 LogCvmfs(kLogFsTraversal, kLogWarning,
357 "Failed to parse value of %s "
358 "to integer: %s (errno=%d)",
359 trimmed_line.c_str(), strerror(errno), errno);
360 continue;
361 }
362 graft_size_ = tmp_size;
363 } else if (info[0] == "checksum") {
364 const std::string hash_str = info[1];
365 const shash::HexPtr hashP(hash_str);
366 if (hashP.IsValid()) {
367 content_hash_ = shash::MkFromHexPtr(hashP);
368 found_checksum = true;
369 } else {
370 LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid checksum value: %s.",
371 info[1].c_str());
372 }
373 continue;
374 } else if (info[0] == "chunk_offsets") {
375 std::vector<std::string> offsets = SplitString(info[1], ',');
376 for (std::vector<std::string>::const_iterator it = offsets.begin();
377 it != offsets.end();
378 it++) {
379 uint64_t val;
380 if (!String2Uint64Parse(*it, &val)) {
381 valid_graft_ = false;
382 LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid chunk offset: %s.",
383 it->c_str());
384 break;
385 }
386 chunk_offsets.push_back(val);
387 }
388 } else if (info[0] == "chunk_checksums") {
389 std::vector<std::string> csums = SplitString(info[1], ',');
390 for (std::vector<std::string>::const_iterator it = csums.begin();
391 it != csums.end();
392 it++) {
393 const shash::HexPtr hashP(*it);
394 if (hashP.IsValid()) {
395 chunk_checksums.push_back(shash::MkFromHexPtr(hashP));
396 } else {
397 LogCvmfs(kLogFsTraversal, kLogWarning,
398 "Invalid chunk checksum "
399 "value: %s.",
400 it->c_str());
401 valid_graft_ = false;
402 break;
403 }
404 }
405 } else if (info[0] == "compression") {
406 SetCompressionAlgorithm(zlib::ParseCompressionAlgorithm(info[1]));
407 }
408 }
409 if (!feof(fp)) {
410 LogCvmfs(kLogFsTraversal, kLogWarning,
411 "Unable to read from catalog "
412 "marker (%s): %s (errno=%d)",
413 graftfile.c_str(), strerror(errno), errno);
414 }
415 fclose(fp);
416 valid_graft_ = valid_graft_ && (graft_size_ > -1) && found_checksum
417 && (chunk_checksums.size() == chunk_offsets.size());
418
419 if (!valid_graft_ || chunk_offsets.empty())
420 return;
421
422 // Parse chunks
423 graft_chunklist_ = new FileChunkList(chunk_offsets.size());
424 off_t last_offset = chunk_offsets[0];
425 if (last_offset != 0) {
426 LogCvmfs(kLogFsTraversal, kLogWarning,
427 "First chunk offset must be 0"
428 " (in graft marker %s).",
429 graftfile.c_str());
430 valid_graft_ = false;
431 }
432 for (unsigned idx = 1; idx < chunk_offsets.size(); idx++) {
433 const off_t cur_offset = chunk_offsets[idx];
434 if (last_offset >= cur_offset) {
435 LogCvmfs(kLogFsTraversal, kLogWarning,
436 "Chunk offsets must be sorted "
437 "in strictly increasing order (in graft marker %s).",
438 graftfile.c_str());
439 valid_graft_ = false;
440 break;
441 }
442 const size_t cur_size = cur_offset - last_offset;
443 graft_chunklist_->PushBack(
444 FileChunk(chunk_checksums[idx - 1], last_offset, cur_size));
445 last_offset = cur_offset;
446 }
447 if (graft_size_ <= last_offset) {
448 LogCvmfs(kLogFsTraversal, kLogWarning,
449 "Last offset must be strictly "
450 "less than total file size (in graft marker %s).",
451 graftfile.c_str());
452 valid_graft_ = false;
453 }
454 graft_chunklist_->PushBack(FileChunk(chunk_checksums.back(), last_offset,
455 graft_size_ - last_offset));
456 }
457
458 } // namespace publish
459