GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/sync_item.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 59 256 23.0%
Branches: 29 360 8.1%

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