Directory: | cvmfs/ |
---|---|
File: | cvmfs/sync_item.h |
Date: | 2025-07-06 02:35:01 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 25 | 102 | 24.5% |
Branches: | 12 | 77 | 15.6% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System | ||
3 | */ | ||
4 | |||
5 | #ifndef CVMFS_SYNC_ITEM_H_ | ||
6 | #define CVMFS_SYNC_ITEM_H_ | ||
7 | |||
8 | #include <sys/types.h> | ||
9 | |||
10 | #if !defined(__APPLE__) | ||
11 | #include <sys/sysmacros.h> | ||
12 | #endif // __APPLE__ | ||
13 | |||
14 | #include <cstring> | ||
15 | #include <map> | ||
16 | #include <string> | ||
17 | |||
18 | #include "crypto/hash.h" | ||
19 | #include "directory_entry.h" | ||
20 | #include "duplex_libarchive.h" | ||
21 | #include "file_chunk.h" | ||
22 | #include "util/platform.h" | ||
23 | #include "util/shared_ptr.h" | ||
24 | |||
25 | class IngestionSource; | ||
26 | |||
27 | namespace publish { | ||
28 | |||
29 | enum SyncItemType { | ||
30 | kItemDir, | ||
31 | kItemFile, | ||
32 | kItemSymlink, | ||
33 | kItemCharacterDevice, | ||
34 | kItemBlockDevice, | ||
35 | kItemFifo, | ||
36 | kItemSocket, | ||
37 | kItemNew, | ||
38 | kItemMarker, | ||
39 | kItemUnknown, | ||
40 | }; | ||
41 | |||
42 | class SyncUnion; | ||
43 | /** | ||
44 | * Every directory entry emitted by the FileSystemTraversal is wrapped in a | ||
45 | * SyncItem structure by the factory method SyncUnion::CreateSyncItem(). | ||
46 | * | ||
47 | * Since we are dealing with a union file system setup, this class represents | ||
48 | * potentially three concrete files: | ||
49 | * - <read-only path>/<filename> | cf. rdonly_stat_ | ||
50 | * - <scratch (read-write) branch>/<filename> | cf. scratch_stat_ | ||
51 | * - <union volume path>/<filename> | cf. union_stat_ | ||
52 | * | ||
53 | * This class caches stat calls to the underlying files in different branches of | ||
54 | * the union file system and hides some interpretation details. | ||
55 | */ | ||
56 | class SyncItem { | ||
57 | // only SyncUnion can create SyncItems (see SyncUnion::CreateSyncItem). | ||
58 | // SyncUnionTarball can create SyncItemTar and SyncItemDummyDir. | ||
59 | |||
60 | public: | ||
61 | SyncItem(); | ||
62 | virtual ~SyncItem(); | ||
63 | |||
64 | 825 | inline bool IsDirectory() const { return IsType(kItemDir); } | |
65 | ✗ | inline bool WasDirectory() const { return WasType(kItemDir); } | |
66 | 625 | inline bool IsRegularFile() const { return IsType(kItemFile); } | |
67 | ✗ | inline bool WasRegularFile() const { return WasType(kItemFile); } | |
68 | 25 | inline bool IsSymlink() const { return IsType(kItemSymlink); } | |
69 | ✗ | inline bool WasSymlink() const { return WasType(kItemSymlink); } | |
70 | 475 | inline bool IsNew() const { return WasType(kItemNew); } | |
71 | inline bool IsTouched() const { | ||
72 | return (GetRdOnlyFiletype() == GetUnionFiletype()) | ||
73 | && (GetRdOnlyFiletype() == GetScratchFiletype()) | ||
74 | && (GetUnionFiletype() == GetScratchFiletype()); | ||
75 | } | ||
76 | ✗ | inline bool IsCharacterDevice() const { return IsType(kItemCharacterDevice); } | |
77 | ✗ | inline bool IsBlockDevice() const { return IsType(kItemBlockDevice); } | |
78 | ✗ | inline bool IsFifo() const { return IsType(kItemFifo); } | |
79 | ✗ | inline bool IsSocket() const { return IsType(kItemSocket); } | |
80 | ✗ | inline bool IsGraftMarker() const { return IsType(kItemMarker); } | |
81 | ✗ | inline bool IsExternalData() const { return external_data_; } | |
82 | ✗ | inline bool IsDirectIo() const { return direct_io_; } | |
83 | |||
84 | 250 | inline bool IsWhiteout() const { return whiteout_; } | |
85 | ✗ | inline bool IsCatalogMarker() const { return filename_ == ".cvmfscatalog"; } | |
86 | ✗ | inline bool IsOpaqueDirectory() const { return IsDirectory() && opaque_; } | |
87 | |||
88 | ✗ | inline bool IsSpecialFile() const { | |
89 | ✗ | return IsCharacterDevice() || IsBlockDevice() || IsFifo() || IsSocket(); | |
90 | } | ||
91 | ✗ | inline bool WasSpecialFile() const { | |
92 | ✗ | return WasType(kItemCharacterDevice) || WasType(kItemBlockDevice) | |
93 | ✗ | || WasType(kItemFifo) || WasType(kItemSocket); | |
94 | } | ||
95 | ✗ | inline bool IsBundleSpec() const { return filename_ == ".cvmfsbundles"; } | |
96 | ✗ | inline bool WasBundleSpec() const { return filename_ == ".cvmfsbundles"; } | |
97 | |||
98 | ✗ | inline unsigned int GetRdevMajor() const { | |
99 | ✗ | assert(IsSpecialFile()); | |
100 | ✗ | StatUnion(true); | |
101 | ✗ | return major(union_stat_.stat.st_rdev); | |
102 | } | ||
103 | |||
104 | ✗ | inline unsigned int GetRdevMinor() const { | |
105 | ✗ | assert(IsSpecialFile()); | |
106 | ✗ | StatUnion(true); | |
107 | ✗ | return minor(union_stat_.stat.st_rdev); | |
108 | } | ||
109 | |||
110 | ✗ | bool HasCatalogMarker() const { return has_catalog_marker_; } | |
111 | ✗ | bool HasGraftMarker() const { return graft_marker_present_; } | |
112 | ✗ | bool HasCompressionAlgorithm() const { return has_compression_algorithm_; } | |
113 | ✗ | bool IsValidGraft() const { return valid_graft_; } | |
114 | ✗ | bool IsChunkedGraft() const { return graft_chunklist_; } | |
115 | |||
116 | ✗ | inline const FileChunkList *GetGraftChunks() const { | |
117 | ✗ | return graft_chunklist_; | |
118 | } | ||
119 | ✗ | inline shash::Any GetContentHash() const { return content_hash_; } | |
120 | ✗ | inline void SetContentHash(const shash::Any &hash) { content_hash_ = hash; } | |
121 | inline bool HasContentHash() const { return !content_hash_.IsNull(); } | ||
122 | ✗ | void SetExternalData(bool val) { external_data_ = val; } | |
123 | ✗ | void SetDirectIo(bool val) { direct_io_ = val; } | |
124 | |||
125 | ✗ | inline zlib::Algorithms GetCompressionAlgorithm() const { | |
126 | ✗ | return compression_algorithm_; | |
127 | } | ||
128 | ✗ | inline void SetCompressionAlgorithm(const zlib::Algorithms &alg) { | |
129 | ✗ | compression_algorithm_ = alg; | |
130 | ✗ | has_compression_algorithm_ = true; | |
131 | } | ||
132 | |||
133 | /** | ||
134 | * Generates a DirectoryEntry that can be directly stored into a catalog db. | ||
135 | * Note: this sets the inode fields to kInvalidInode as well as the link | ||
136 | * count to 1 if MaskHardlink() has been called before (cf. OverlayFS) | ||
137 | * | ||
138 | * If nanosecond timestamps are off, the directory entry will have a | ||
139 | * default initialized, negative nanosecond timestamp and as a result | ||
140 | * the corresponding field in the catalog table will be NULL. | ||
141 | * | ||
142 | * @return a DirectoryEntry structure to be written into a catalog | ||
143 | */ | ||
144 | virtual catalog::DirectoryEntryBase CreateBasicCatalogDirent( | ||
145 | bool enable_mtime_ns) const = 0; | ||
146 | |||
147 | 1550 | inline std::string GetRelativePath() const { | |
148 | 1550 | return (relative_parent_path_.empty()) | |
149 | 300 | ? filename_ | |
150 | 1250 | : relative_parent_path_ | |
151 |
9/21✓ Branch 0 taken 300 times.
✓ Branch 1 taken 1250 times.
✓ Branch 3 taken 300 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1250 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 13 taken 1250 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1250 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 1250 times.
✓ Branch 19 taken 300 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 1550 times.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
|
3100 | + (filename_.empty() ? "" : ("/" + filename_)); |
152 | } | ||
153 | |||
154 | std::string GetRdOnlyPath() const; | ||
155 | std::string GetUnionPath() const; | ||
156 | std::string GetScratchPath() const; | ||
157 | |||
158 | void MarkAsWhiteout(const std::string &actual_filename); | ||
159 | void MarkAsOpaqueDirectory(); | ||
160 | |||
161 | /** | ||
162 | * Union file systems (i.e. OverlayFS) might not properly support hardlinks, | ||
163 | * forcing us to ignore them during publishing. A 'masked hardlink' will be | ||
164 | * treated as a normal file (linkcount == 1). Hence, any created hardlinks | ||
165 | * will be broken up into individual files with differing inodes. | ||
166 | */ | ||
167 | ✗ | inline void MaskHardlink() { masked_hardlink_ = true; } | |
168 | ✗ | inline bool HasHardlinks() const { | |
169 | ✗ | return !masked_hardlink_ && GetUnionLinkcount() > 1; | |
170 | } | ||
171 | |||
172 | unsigned int GetRdOnlyLinkcount() const; | ||
173 | uint64_t GetRdOnlyInode() const; | ||
174 | unsigned int GetUnionLinkcount() const; | ||
175 | uint64_t GetUnionInode() const; | ||
176 | uint64_t GetScratchSize() const; | ||
177 | uint64_t GetRdOnlySize() const; | ||
178 | |||
179 | 750 | inline std::string filename() const { return filename_; } | |
180 | ✗ | inline std::string relative_parent_path() const { | |
181 | ✗ | return relative_parent_path_; | |
182 | } | ||
183 | |||
184 | virtual IngestionSource *CreateIngestionSource() const = 0; | ||
185 | virtual void MakePlaceholderDirectory() const = 0; | ||
186 | ✗ | void SetCatalogMarker() { has_catalog_marker_ = true; } | |
187 | |||
188 | ✗ | bool operator==(const SyncItem &other) const { | |
189 | ✗ | return ((relative_parent_path_ == other.relative_parent_path_) | |
190 | ✗ | && (filename_ == other.filename_)); | |
191 | } | ||
192 | |||
193 | protected: | ||
194 | /** | ||
195 | * create a new SyncItem | ||
196 | * Note: SyncItems cannot be created by any using code. SyncUnion will take | ||
197 | * care of their creating through a factory method to make sure they | ||
198 | * are initialised correctly (whiteout, hardlink handling, ...) | ||
199 | * | ||
200 | * @param dirPath the RELATIVE path to the file | ||
201 | * @param filename the name of the file ;-) | ||
202 | * @param entryType well... | ||
203 | */ | ||
204 | SyncItem(const std::string &relative_parent_path, | ||
205 | const std::string &filename, | ||
206 | const SyncUnion *union_engine, | ||
207 | const SyncItemType entry_type); | ||
208 | |||
209 | ✗ | inline platform_stat64 GetUnionStat() const { | |
210 | ✗ | StatUnion(); | |
211 | ✗ | return union_stat_.stat; | |
212 | } | ||
213 | |||
214 | SyncItemType GetRdOnlyFiletype() const; | ||
215 | SyncItemType GetUnionFiletype() const; | ||
216 | |||
217 | virtual SyncItemType GetScratchFiletype() const = 0; | ||
218 | |||
219 | /** | ||
220 | * Checks if the SyncItem _is_ the given file type (file, dir, symlink, ...) | ||
221 | * in the union file system volume. Hence: After the publish operation, the | ||
222 | * file will be this type in CVMFS. | ||
223 | * @param expected_type the file type to be checked against | ||
224 | * @return true if file type matches the expected type | ||
225 | */ | ||
226 | virtual bool IsType(const SyncItemType expected_type) const = 0; | ||
227 | |||
228 | /** | ||
229 | * Checks if the SyncItem _was_ the given file type (file, dir, symlink, ...) | ||
230 | * in CVMFS (or the lower layer of the union file system). Hence: Before the | ||
231 | * current transaction the file _was_ this type in CVMFS. | ||
232 | * @param expected_type the file type to be checked against | ||
233 | * @return true if file type was the expected type in CVMFS | ||
234 | */ | ||
235 | 475 | inline bool WasType(const SyncItemType expected_type) const { | |
236 |
2/2✓ Branch 0 taken 425 times.
✓ Branch 1 taken 50 times.
|
475 | if (rdonly_type_ == kItemUnknown) { |
237 | 425 | rdonly_type_ = GetRdOnlyFiletype(); | |
238 | } | ||
239 | 475 | return rdonly_type_ == expected_type; | |
240 | } | ||
241 | |||
242 | /** | ||
243 | * Structure to cache stat calls to the different file locations. | ||
244 | */ | ||
245 | struct EntryStat { | ||
246 | 1550 | EntryStat() : obtained(false), error_code(0) { | |
247 | 1550 | memset(&stat, 0, sizeof(stat)); | |
248 | 1550 | } | |
249 | |||
250 | ✗ | inline SyncItemType GetSyncItemType() const { | |
251 | ✗ | assert(obtained); | |
252 | ✗ | if (S_ISREG(stat.st_mode)) | |
253 | ✗ | return kItemFile; | |
254 | ✗ | if (S_ISLNK(stat.st_mode)) | |
255 | ✗ | return kItemSymlink; | |
256 | ✗ | if (S_ISDIR(stat.st_mode)) | |
257 | ✗ | return kItemDir; | |
258 | ✗ | if (S_ISFIFO(stat.st_mode)) | |
259 | ✗ | return kItemFifo; | |
260 | ✗ | if (S_ISSOCK(stat.st_mode)) | |
261 | ✗ | return kItemSocket; | |
262 | ✗ | if (S_ISCHR(stat.st_mode)) | |
263 | ✗ | return kItemCharacterDevice; | |
264 | ✗ | if (S_ISBLK(stat.st_mode)) | |
265 | ✗ | return kItemBlockDevice; | |
266 | ✗ | return kItemUnknown; | |
267 | } | ||
268 | |||
269 | bool obtained; /**< false at the beginning, true after first stat call */ | ||
270 | int error_code; /**< errno value of the stat call */ | ||
271 | platform_stat64 stat; | ||
272 | }; | ||
273 | |||
274 | static void StatGeneric(const std::string &path, | ||
275 | EntryStat *info, | ||
276 | const bool refresh); | ||
277 | SyncItemType GetGenericFiletype(const EntryStat &stat) const; | ||
278 | void CheckMarkerFiles(); | ||
279 | |||
280 | mutable SyncItemType rdonly_type_; | ||
281 | mutable EntryStat scratch_stat_; | ||
282 | |||
283 | ssize_t graft_size_; | ||
284 | |||
285 | // The hash of regular file's content | ||
286 | shash::Any content_hash_; | ||
287 | |||
288 | mutable SyncItemType scratch_type_; | ||
289 | |||
290 | private: | ||
291 | void CheckCatalogMarker(); | ||
292 | |||
293 | std::string filename_; | ||
294 | |||
295 | std::string GetGraftMarkerPath() const; | ||
296 | void CheckGraft(); | ||
297 | |||
298 | const SyncUnion *union_engine_; /**< this SyncUnion created this object */ | ||
299 | |||
300 | mutable EntryStat rdonly_stat_; | ||
301 | mutable EntryStat union_stat_; | ||
302 | |||
303 | bool whiteout_; /**< SyncUnion marked this as whiteout */ | ||
304 | bool opaque_; /**< SyncUnion marked this as opaque dir*/ | ||
305 | bool masked_hardlink_; /**< SyncUnion masked out the linkcount */ | ||
306 | bool has_catalog_marker_; /**< directory containing .cvmfscatalog */ | ||
307 | bool valid_graft_; /**< checksum and size in graft marker */ | ||
308 | bool graft_marker_present_; /**< .cvmfsgraft-$filename exists */ | ||
309 | |||
310 | bool external_data_; | ||
311 | bool direct_io_; | ||
312 | std::string relative_parent_path_; | ||
313 | |||
314 | /** | ||
315 | * Chunklist from graft. Not initialized by default to save memory. | ||
316 | */ | ||
317 | FileChunkList *graft_chunklist_; | ||
318 | |||
319 | // The compression algorithm for the file | ||
320 | zlib::Algorithms compression_algorithm_; | ||
321 | // The compression algorithm has been set explicitly | ||
322 | bool has_compression_algorithm_; | ||
323 | |||
324 | // Lazy evaluation and caching of results of file stats | ||
325 | 425 | inline void StatRdOnly(const bool refresh = false) const { | |
326 | 425 | StatGeneric(GetRdOnlyPath(), &rdonly_stat_, refresh); | |
327 | 425 | } | |
328 | ✗ | inline void StatUnion(const bool refresh = false) const { | |
329 | ✗ | StatGeneric(GetUnionPath(), &union_stat_, refresh); | |
330 | } | ||
331 | virtual void StatScratch(const bool refresh) const = 0; | ||
332 | }; | ||
333 | |||
334 | typedef std::map<std::string, SharedPtr<SyncItem> > SyncItemList; | ||
335 | |||
336 | class SyncItemNative : public SyncItem { | ||
337 | friend class SyncUnion; | ||
338 | virtual catalog::DirectoryEntryBase CreateBasicCatalogDirent( | ||
339 | bool enable_mtime_ns) const; | ||
340 | virtual IngestionSource *CreateIngestionSource() const; | ||
341 | ✗ | virtual void MakePlaceholderDirectory() const { assert(false); } | |
342 | virtual SyncItemType GetScratchFiletype() const; | ||
343 | virtual bool IsType(const SyncItemType expected_type) const; | ||
344 | ✗ | virtual void StatScratch(const bool refresh) const { | |
345 | ✗ | StatGeneric(GetScratchPath(), &scratch_stat_, refresh); | |
346 | } | ||
347 | |||
348 | protected: | ||
349 | 125 | SyncItemNative(const std::string &relative_parent_path, | |
350 | const std::string &filename, const SyncUnion *union_engine, | ||
351 | const SyncItemType entry_type) | ||
352 | 125 | : SyncItem(relative_parent_path, filename, union_engine, entry_type) { | |
353 |
1/2✓ Branch 1 taken 125 times.
✗ Branch 2 not taken.
|
125 | CheckMarkerFiles(); |
354 | 125 | } | |
355 | }; | ||
356 | |||
357 | } // namespace publish | ||
358 | |||
359 | #endif // CVMFS_SYNC_ITEM_H_ | ||
360 |