Directory: | cvmfs/ |
---|---|
File: | cvmfs/sync_item.cc |
Date: | 2025-04-20 02:34:28 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 59 | 259 | 22.8% |
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 "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( | |
205 | bool enable_mtime_ns) const | ||
206 | { | ||
207 | ✗ | catalog::DirectoryEntryBase dirent; | |
208 | |||
209 | // inode and parent inode is determined at runtime of client | ||
210 | ✗ | dirent.inode_ = catalog::DirectoryEntry::kInvalidInode; | |
211 | |||
212 | // this might mask the actual link count in case hardlinks are not supported | ||
213 | // (i.e. on setups using OverlayFS) | ||
214 | ✗ | dirent.linkcount_ = HasHardlinks() ? this->GetUnionStat().st_nlink : 1; | |
215 | |||
216 | ✗ | dirent.mode_ = this->GetUnionStat().st_mode; | |
217 | ✗ | dirent.uid_ = this->GetUnionStat().st_uid; | |
218 | ✗ | dirent.gid_ = this->GetUnionStat().st_gid; | |
219 | ✗ | dirent.size_ = graft_size_ > -1 ? graft_size_ : | |
220 | ✗ | this->GetUnionStat().st_size; | |
221 | ✗ | dirent.mtime_ = this->GetUnionStat().st_mtime; | |
222 | ✗ | dirent.checksum_ = this->GetContentHash(); | |
223 | ✗ | dirent.is_external_file_ = this->IsExternalData(); | |
224 | ✗ | dirent.is_direct_io_ = this->IsDirectIo(); | |
225 | ✗ | dirent.compression_algorithm_ = this->GetCompressionAlgorithm(); | |
226 | |||
227 | ✗ | dirent.name_.Assign(filename().data(), filename().length()); | |
228 | |||
229 | ✗ | if (this->IsSymlink()) { | |
230 | char slnk[PATH_MAX+1]; | ||
231 | const ssize_t length = | ||
232 | ✗ | readlink((this->GetUnionPath()).c_str(), slnk, PATH_MAX); | |
233 | ✗ | assert(length >= 0); | |
234 | ✗ | dirent.symlink_.Assign(slnk, length); | |
235 | } | ||
236 | |||
237 | ✗ | if (this->IsCharacterDevice() || this->IsBlockDevice()) { | |
238 | ✗ | dirent.size_ = makedev(GetRdevMajor(), GetRdevMinor()); | |
239 | } | ||
240 | |||
241 | ✗ | if (enable_mtime_ns) { | |
242 | #ifdef __APPLE__ | ||
243 | dirent.mtime_ns_ = static_cast<int32_t>( | ||
244 | this->GetUnionStat().st_mtimespec.tv_nsec); | ||
245 | #else | ||
246 | ✗ | dirent.mtime_ns_ = static_cast<int32_t>( | |
247 | ✗ | this->GetUnionStat().st_mtim.tv_nsec); | |
248 | #endif | ||
249 | } | ||
250 | |||
251 | ✗ | return dirent; | |
252 | } | ||
253 | |||
254 | |||
255 | 17 | std::string SyncItem::GetRdOnlyPath() const { | |
256 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
17 | const string relative_path = GetRelativePath().empty() ? |
257 |
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(); |
258 |
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; |
259 | 17 | } | |
260 | |||
261 | 14 | std::string SyncItem::GetUnionPath() const { | |
262 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | const string relative_path = GetRelativePath().empty() ? |
263 |
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(); |
264 |
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; |
265 | 14 | } | |
266 | |||
267 | ✗ | std::string SyncItem::GetScratchPath() const { | |
268 | ✗ | const string relative_path = GetRelativePath().empty() ? | |
269 | ✗ | "" : "/" + GetRelativePath(); | |
270 | ✗ | return union_engine_->scratch_path() + relative_path; | |
271 | // return union_engine_->scratch_path() + filename(); | ||
272 | } | ||
273 | |||
274 | 5 | void SyncItem::CheckMarkerFiles() { | |
275 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | if (IsRegularFile()) { |
276 | ✗ | CheckGraft(); | |
277 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | } else if (IsDirectory()) { |
278 | 5 | CheckCatalogMarker(); | |
279 | } | ||
280 | 5 | } | |
281 | |||
282 | 5 | void SyncItem::CheckCatalogMarker() { | |
283 |
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"); |
284 | 5 | EntryStat stat; | |
285 | 5 | StatGeneric(path, &stat, false); | |
286 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (stat.error_code) { |
287 | 5 | has_catalog_marker_ = false; | |
288 | 5 | return; | |
289 | } | ||
290 | ✗ | if (stat.GetSyncItemType() == kItemFile) { | |
291 | ✗ | has_catalog_marker_ = true; | |
292 | ✗ | return; | |
293 | } | ||
294 | ✗ | PANIC(kLogStderr, "Error: '%s' is not a regular file.", path.c_str()); | |
295 | 5 | } | |
296 | |||
297 | |||
298 | ✗ | std::string SyncItem::GetGraftMarkerPath() const { | |
299 | ✗ | return union_engine_->scratch_path() + "/" + | |
300 | ✗ | ((relative_parent_path_.empty()) ? | |
301 | ✗ | ".cvmfsgraft-" + filename_ : | |
302 | ✗ | relative_parent_path_ + (filename_.empty() ? "" : | |
303 | ✗ | ("/.cvmfsgraft-" + filename_))); | |
304 | } | ||
305 | |||
306 | ✗ | void SyncItem::CheckGraft() { | |
307 | ✗ | valid_graft_ = false; | |
308 | ✗ | bool found_checksum = false; | |
309 | ✗ | std::string checksum_type; | |
310 | ✗ | std::string checksum_value; | |
311 | ✗ | 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, "Unable to open graft file " | |
321 | "(%s): %s (errno=%d)", | ||
322 | ✗ | graftfile.c_str(), strerror(errno), errno); | |
323 | } | ||
324 | ✗ | return; | |
325 | } | ||
326 | ✗ | graft_marker_present_ = true; | |
327 | ✗ | valid_graft_ = true; | |
328 | ✗ | std::string line; | |
329 | ✗ | std::vector<std::string> contents; | |
330 | |||
331 | ✗ | std::vector<off_t> chunk_offsets; | |
332 | ✗ | std::vector<shash::Any> chunk_checksums; | |
333 | |||
334 | ✗ | while (GetLineFile(fp, &line)) { | |
335 | ✗ | std::string trimmed_line = Trim(line); | |
336 | |||
337 | ✗ | if (!trimmed_line.size()) {continue;} | |
338 | ✗ | if (trimmed_line[0] == '#') {continue;} | |
339 | |||
340 | ✗ | std::vector<std::string> info = SplitStringBounded(2, trimmed_line, '='); | |
341 | |||
342 | ✗ | if (info.size() != 2) { | |
343 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid line in graft file: %s", | |
344 | trimmed_line.c_str()); | ||
345 | } | ||
346 | ✗ | info[0] = Trim(info[0]); | |
347 | ✗ | info[1] = Trim(info[1]); | |
348 | ✗ | if (info[0] == "size") { | |
349 | uint64_t tmp_size; | ||
350 | ✗ | if (!String2Uint64Parse(info[1], &tmp_size)) { | |
351 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Failed to parse value of %s " | |
352 | "to integer: %s (errno=%d)", trimmed_line.c_str(), | ||
353 | ✗ | strerror(errno), errno); | |
354 | ✗ | continue; | |
355 | } | ||
356 | ✗ | graft_size_ = tmp_size; | |
357 | ✗ | } else if (info[0] == "checksum") { | |
358 | ✗ | std::string hash_str = info[1]; | |
359 | ✗ | shash::HexPtr hashP(hash_str); | |
360 | ✗ | if (hashP.IsValid()) { | |
361 | ✗ | content_hash_ = shash::MkFromHexPtr(hashP); | |
362 | ✗ | found_checksum = true; | |
363 | } else { | ||
364 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid checksum value: %s.", | |
365 | ✗ | info[1].c_str()); | |
366 | } | ||
367 | ✗ | continue; | |
368 | ✗ | } else if (info[0] == "chunk_offsets") { | |
369 | ✗ | std::vector<std::string> offsets = SplitString(info[1], ','); | |
370 | ✗ | for (std::vector<std::string>::const_iterator it = offsets.begin(); | |
371 | ✗ | it != offsets.end(); it++) | |
372 | { | ||
373 | uint64_t val; | ||
374 | ✗ | if (!String2Uint64Parse(*it, &val)) { | |
375 | ✗ | valid_graft_ = false; | |
376 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid chunk offset: %s.", | |
377 | it->c_str()); | ||
378 | ✗ | break; | |
379 | } | ||
380 | ✗ | chunk_offsets.push_back(val); | |
381 | } | ||
382 | ✗ | } else if (info[0] == "chunk_checksums") { | |
383 | ✗ | std::vector<std::string> csums = SplitString(info[1], ','); | |
384 | ✗ | for (std::vector<std::string>::const_iterator it = csums.begin(); | |
385 | ✗ | it != csums.end(); it++) | |
386 | { | ||
387 | ✗ | shash::HexPtr hashP(*it); | |
388 | ✗ | if (hashP.IsValid()) { | |
389 | ✗ | chunk_checksums.push_back(shash::MkFromHexPtr(hashP)); | |
390 | } else { | ||
391 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid chunk checksum " | |
392 | "value: %s.", it->c_str()); | ||
393 | ✗ | valid_graft_ = false; | |
394 | ✗ | break; | |
395 | } | ||
396 | } | ||
397 | ✗ | } else if (info[0] == "compression") { | |
398 | ✗ | SetCompressionAlgorithm(zlib::ParseCompressionAlgorithm(info[1])); | |
399 | } | ||
400 | } | ||
401 | ✗ | if (!feof(fp)) { | |
402 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Unable to read from catalog " | |
403 | "marker (%s): %s (errno=%d)", | ||
404 | ✗ | graftfile.c_str(), strerror(errno), errno); | |
405 | } | ||
406 | ✗ | fclose(fp); | |
407 | ✗ | valid_graft_ = valid_graft_ && (graft_size_ > -1) && found_checksum | |
408 | ✗ | && (chunk_checksums.size() == chunk_offsets.size()); | |
409 | |||
410 | ✗ | if (!valid_graft_ || chunk_offsets.empty()) | |
411 | ✗ | return; | |
412 | |||
413 | // Parse chunks | ||
414 | ✗ | graft_chunklist_ = new FileChunkList(chunk_offsets.size()); | |
415 | ✗ | off_t last_offset = chunk_offsets[0]; | |
416 | ✗ | if (last_offset != 0) { | |
417 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "First chunk offset must be 0" | |
418 | " (in graft marker %s).", graftfile.c_str()); | ||
419 | ✗ | valid_graft_ = false; | |
420 | } | ||
421 | ✗ | for (unsigned idx = 1; idx < chunk_offsets.size(); idx++) { | |
422 | ✗ | off_t cur_offset = chunk_offsets[idx]; | |
423 | ✗ | if (last_offset >= cur_offset) { | |
424 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Chunk offsets must be sorted " | |
425 | "in strictly increasing order (in graft marker %s).", | ||
426 | graftfile.c_str()); | ||
427 | ✗ | valid_graft_ = false; | |
428 | ✗ | break; | |
429 | } | ||
430 | ✗ | size_t cur_size = cur_offset - last_offset; | |
431 | ✗ | graft_chunklist_->PushBack(FileChunk(chunk_checksums[idx - 1], | |
432 | last_offset, | ||
433 | cur_size)); | ||
434 | ✗ | last_offset = cur_offset; | |
435 | } | ||
436 | ✗ | if (graft_size_ <= last_offset) { | |
437 | ✗ | LogCvmfs(kLogFsTraversal, kLogWarning, "Last offset must be strictly " | |
438 | "less than total file size (in graft marker %s).", | ||
439 | graftfile.c_str()); | ||
440 | ✗ | valid_graft_ = false; | |
441 | } | ||
442 | ✗ | graft_chunklist_->PushBack(FileChunk(chunk_checksums.back(), | |
443 | last_offset, | ||
444 | ✗ | graft_size_ - last_offset)); | |
445 | } | ||
446 | |||
447 | } // namespace publish | ||
448 |