GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/directory_entry.h
Date: 2026-08-30 02:40:36
Exec Total Coverage
Lines: 149 156 95.5%
Branches: 25 44 56.8%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * Data wrappers for single dentries. In addition to the normal file meta data
5 * it manages bookkeeping data specific to cvmfs such as the associated catalog.
6 */
7
8 #ifndef CVMFS_DIRECTORY_ENTRY_H_
9 #define CVMFS_DIRECTORY_ENTRY_H_
10
11 #include <sys/stat.h>
12 #include <sys/types.h>
13
14 #include <cassert>
15 #include <cstring>
16 #include <string>
17 #include <vector>
18
19 #include "bigvector.h"
20 #include "compression/compression.h"
21 #include "crypto/hash.h"
22 #include "shortstring.h"
23 #include "util/platform.h"
24
25 namespace publish {
26 class SyncItem;
27 class SyncItemNative;
28 class SyncItemTar;
29 class SyncItemDummyDir;
30 class SyncItemDummyCatalog;
31 class SyncItemDummyFile;
32 } // namespace publish
33 namespace swissknife {
34 class CommandMigrate;
35 class CommandOverlay;
36 class IngestSQL;
37 } // namespace swissknife
38
39 namespace catalog {
40
41 // Create DirectoryEntries for unit test purposes.
42 class DirectoryEntryTestFactory;
43
44 class MockCatalogManager;
45 class Catalog;
46 class WritableCatalogManager;
47
48 template<class CatalogMgrT>
49 class CatalogBalancer;
50 typedef uint64_t inode_t;
51
52 enum SpecialDirents {
53 kDirentNormal = 0,
54 kDirentNegative,
55 };
56
57 /**
58 * Wrapper around struct dirent. Only contains file system related meta data
59 * for a directory entry.
60 * TODO(jblomer): separation to DirectoryEntry not quite clear: this one also
61 * contains hash, compression algorithm and external flag
62 */
63 class DirectoryEntryBase {
64 // For testing the catalog balancing
65 friend class CatalogBalancer<MockCatalogManager>;
66 // Create .cvmfscatalog and .cvmfsautocatalog files
67 friend class CatalogBalancer<WritableCatalogManager>;
68 // Simplify creation of DirectoryEntry objects for write back
69 friend class publish::SyncItem;
70 friend class publish::SyncItemNative;
71 friend class publish::SyncItemTar;
72 friend class publish::SyncItemDummyDir;
73 friend class publish::SyncItemDummyCatalog;
74 friend class publish::SyncItemDummyFile;
75 friend class swissknife::CommandOverlay;
76 friend class swissknife::IngestSQL; // TODO(vvolkl): can probably avoided
77 // with new setters
78 // Simplify file system like _touch_ of DirectoryEntry objects
79 friend class SqlDirentTouch;
80 // Allow creation of virtual directories and files
81 friend class VirtualCatalog;
82
83 public:
84 static const inode_t kInvalidInode = 0;
85
86 /**
87 * Used in the swissknife for sanity checks and catalog migration. If
88 * anything is added, also adjust PrintDifferences in swissknife::CommandDiff
89 * and CommandCheck::CompareEntries
90 */
91 struct Difference {
92 static const unsigned int kIdentical = 0x00000;
93 static const unsigned int kName = 0x00001;
94 static const unsigned int kLinkcount = 0x00002;
95 static const unsigned int kSize = 0x00004;
96 static const unsigned int kMode = 0x00008;
97 static const unsigned int kMtime = 0x00010;
98 static const unsigned int kSymlink = 0x00020;
99 static const unsigned int kChecksum = 0x00040;
100 static const unsigned int kHardlinkGroup = 0x00080;
101 static const unsigned int kNestedCatalogTransitionFlags = 0x00100;
102 static const unsigned int kChunkedFileFlag = 0x00200;
103 static const unsigned int kHasXattrsFlag = 0x00400;
104 static const unsigned int kExternalFileFlag = 0x00800;
105 static const unsigned int kBindMountpointFlag = 0x01000;
106 static const unsigned int kHiddenFlag = 0x02000;
107 static const unsigned int kDirectIoFlag = 0x04000;
108 static const unsigned int kBundleTriggerFlag = 0x08000;
109 static const unsigned int kUid = 0x10000;
110 static const unsigned int kGid = 0x20000;
111 static const unsigned int kVolatileFlag = 0x40000;
112 };
113 typedef unsigned int Differences;
114
115 /**
116 * Zero-constructed DirectoryEntry objects are unusable as such.
117 */
118 48127920 inline DirectoryEntryBase()
119 48127920 : inode_(kInvalidInode)
120 48127920 , mode_(0)
121 48127920 , uid_(0)
122 48127920 , gid_(0)
123 48127920 , size_(0)
124 48127920 , mtime_(0)
125 48127920 , mtime_ns_(-1)
126 48127920 , linkcount_(1) // generally a normal file has linkcount 1 -> default
127 48127920 , has_xattrs_(false)
128 48127920 , is_external_file_(false)
129 48127920 , is_direct_io_(false)
130 48127920 , is_bundle_trigger_(false)
131 48127920 , is_volatile_(false)
132
1/2
✓ Branch 3 taken 48127920 times.
✗ Branch 4 not taken.
48127920 , compression_algorithm_(zlib::kZlibDefault) { }
133
134 47450 inline bool IsRegular() const { return S_ISREG(mode_); }
135 57715 inline bool IsLink() const { return S_ISLNK(mode_); }
136 58273 inline bool IsDirectory() const { return S_ISDIR(mode_); }
137 21145 inline bool IsFifo() const { return S_ISFIFO(mode_); }
138 20969 inline bool IsSocket() const { return S_ISSOCK(mode_); }
139 50891 inline bool IsCharDev() const { return S_ISCHR(mode_); }
140 50891 inline bool IsBlockDev() const { return S_ISBLK(mode_); }
141 21145 inline bool IsSpecial() const {
142
5/8
✓ Branch 1 taken 20969 times.
✓ Branch 2 taken 176 times.
✓ Branch 4 taken 20969 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 20969 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 20969 times.
21145 return IsFifo() || IsSocket() || IsCharDev() || IsBlockDev();
143 }
144 31943 inline bool IsExternalFile() const { return is_external_file_; }
145 15875 inline bool IsDirectIo() const { return is_direct_io_; }
146 16051 inline bool IsBundleTrigger() const { return is_bundle_trigger_; }
147 15875 inline bool IsVolatile() const { return is_volatile_; }
148 39212 inline bool HasXattrs() const { return has_xattrs_; }
149 30907 inline bool HasMtimeNs() const { return mtime_ns_ >= 0; }
150
151 31108 inline inode_t inode() const { return inode_; }
152 15521 inline uint32_t linkcount() const { return linkcount_; }
153 71407 inline NameString name() const { return name_; }
154 4673 inline LinkString symlink() const { return symlink_; }
155 4078 inline time_t mtime() const { return mtime_; }
156 4018 inline int32_t mtime_ns() const { return mtime_ns_; }
157 4078 inline unsigned int mode() const { return mode_; }
158 5244 inline uid_t uid() const { return uid_; }
159 5244 inline gid_t gid() const { return gid_; }
160 9877 inline shash::Any checksum() const { return checksum_; }
161 40770 inline const shash::Any *checksum_ptr() const { return &checksum_; }
162 90 inline shash::Algorithms hash_algorithm() const {
163 90 return checksum_.algorithm;
164 }
165 29101 inline uint64_t size() const {
166
2/2
✓ Branch 1 taken 354 times.
✓ Branch 2 taken 28747 times.
29101 if (IsLink())
167 354 return symlink().GetLength();
168
3/6
✓ Branch 1 taken 28747 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 28747 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28747 times.
28747 if (IsBlockDev() || IsCharDev())
169 return 0;
170 28747 return size_;
171 }
172 1175 inline dev_t rdev() const {
173
3/6
✓ Branch 1 taken 1175 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1175 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1175 times.
1175 if (IsBlockDev() || IsCharDev())
174 return size_;
175 1175 return 1;
176 }
177 20746 inline std::string GetFullPath(const std::string &parent_directory) const {
178 20746 std::string file_path = parent_directory + "/";
179
3/6
✓ Branch 1 taken 20746 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 20746 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 20746 times.
✗ Branch 10 not taken.
20746 file_path.append(name().GetChars(), name().GetLength());
180 20746 return file_path;
181 }
182
183 2402 inline void set_inode(const inode_t inode) { inode_ = inode; }
184 9658 inline void set_linkcount(const uint32_t linkcount) {
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9658 times.
9658 assert(linkcount > 0);
186 9658 linkcount_ = linkcount;
187 9658 }
188 inline void set_symlink(const LinkString &symlink) { symlink_ = symlink; }
189 19532 inline void set_has_xattrs(const bool has_xattrs) {
190 19532 has_xattrs_ = has_xattrs;
191 19532 }
192 inline void set_is_bundle_trigger(bool val) { is_bundle_trigger_ = val; }
193
194 // Use fixed metadata for implicit lease ancestors. Epoch mtime is
195 // reproducible and distinguishes these entries from publisher data.
196 98 inline void SetImplicitDirectoryMetadata() {
197 98 mode_ = S_IFDIR | 0755;
198 98 uid_ = 0;
199 98 gid_ = 0;
200 98 size_ = 0;
201 98 mtime_ = 0;
202 98 mtime_ns_ = -1;
203
3/6
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 98 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 98 times.
✗ Branch 9 not taken.
98 symlink_ = LinkString("");
204 98 has_xattrs_ = false;
205 98 checksum_ = shash::Any();
206 98 compression_algorithm_ = zlib::kZlibDefault;
207 98 is_external_file_ = false;
208 98 is_direct_io_ = false;
209 98 is_bundle_trigger_ = false;
210 98 }
211
212 12277 inline zlib::Algorithms compression_algorithm() const {
213 12277 return compression_algorithm_;
214 }
215
216 /**
217 * Converts to a stat struct as required by many Fuse callbacks.
218 * @return the struct stat for this DirectoryEntry
219 */
220 1115 inline struct stat GetStatStructure() const {
221 struct stat s;
222 1115 memset(&s, 0, sizeof(s));
223 1115 s.st_dev = 1;
224 1115 s.st_ino = inode_;
225 1115 s.st_mode = mode_;
226 1115 s.st_nlink = linkcount();
227 1115 s.st_uid = uid();
228 1115 s.st_gid = gid();
229 1115 s.st_rdev = rdev();
230 1115 s.st_size = static_cast<off_t>(size());
231 1115 s.st_blksize = 4096; // will be ignored by Fuse
232 1115 s.st_blocks = static_cast<blkcnt_t>(1 + size() / 512);
233 1115 s.st_atime = mtime_;
234 1115 s.st_mtime = mtime_;
235 1115 s.st_ctime = mtime_;
236
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1115 times.
1115 if (HasMtimeNs()) {
237 #ifdef __APPLE__
238 s.st_atimespec.tv_nsec = mtime_ns_;
239 s.st_mtimespec.tv_nsec = mtime_ns_;
240 s.st_ctimespec.tv_nsec = mtime_ns_;
241 #else
242 s.st_atim.tv_nsec = mtime_ns_;
243 s.st_mtim.tv_nsec = mtime_ns_;
244 s.st_ctim.tv_nsec = mtime_ns_;
245 #endif
246 }
247 1115 return s;
248 }
249
250 Differences CompareTo(const DirectoryEntryBase &other) const;
251 inline bool operator==(const DirectoryEntryBase &other) const {
252 return CompareTo(other) == Difference::kIdentical;
253 }
254 inline bool operator!=(const DirectoryEntryBase &other) const {
255 return !(*this == other);
256 }
257
258 protected:
259 // Inodes are generated based on the rowid of the entry in the file catalog.
260 inode_t inode_;
261
262 // Data from struct stat
263 NameString name_;
264 unsigned int mode_;
265 uid_t uid_;
266 gid_t gid_;
267 uint64_t size_;
268 time_t mtime_;
269 // nanosecond part of the mtime. Only valid if non-negative
270 int32_t mtime_ns_;
271 LinkString symlink_;
272 uint32_t linkcount_;
273 // In order to save memory, we only indicate if a directory entry has custom
274 // extended attributes. Another call to the file catalog is necessary to
275 // get them.
276 bool has_xattrs_;
277
278 // The cryptographic hash is not part of the file system intrinsics, though
279 // it can be computed just using the file contents. We therefore put it in
280 // this base class.
281 shash::Any checksum_;
282
283 bool is_external_file_;
284 bool is_direct_io_;
285 bool is_bundle_trigger_;
286 bool is_volatile_;
287
288 // The compression algorithm
289 zlib::Algorithms compression_algorithm_;
290 };
291
292
293 /**
294 * In addition to the file system meta-data covered by DirectoryEntryBase,
295 * DirectoryEntries contain cvmfs-specific meta data. Currently these are the
296 * following things:
297 * - Pointer to the originating catalog
298 * - Markers for nested catalog transition points (mountpoint and root entry)
299 * - Transient marker storing the time of caching (Fuse page caches).
300 * This is required to invalidate caches after a catalog update
301 * - Hardlink group used to emulate hardlinks in cvmfs
302 */
303 class DirectoryEntry : public DirectoryEntryBase {
304 // Simplify creation of DirectoryEntry objects
305 friend class SqlLookup;
306 // Simplify write of DirectoryEntry objects in database
307 friend class SqlDirentWrite;
308 // For fixing DirectoryEntry glitches
309 friend class swissknife::CommandMigrate;
310 // TODO(rmeusel): remove this dependency
311 friend class WritableCatalogManager;
312 // Create DirectoryEntries for unit test purposes.
313 friend class DirectoryEntryTestFactory;
314
315 public:
316 /**
317 * This is _kind of_ a copy constructor allowing us to create
318 * DirectoryEntries directly from DirectoryEntryBase objects. We make it
319 * explicit, to disallow black magic from happening. It uses the copy
320 * constructor of DirectoryEntryBase and initializes the additional fields of
321 * DirectoryEntry.
322 */
323 9657 inline explicit DirectoryEntry(const DirectoryEntryBase &base)
324 9657 : DirectoryEntryBase(base)
325 9657 , hardlink_group_(0)
326 9657 , is_nested_catalog_root_(false)
327 9657 , is_nested_catalog_mountpoint_(false)
328 9657 , is_bind_mountpoint_(false)
329 9657 , is_chunked_file_(false)
330 9657 , is_hidden_(false)
331 9657 , is_negative_(false) { }
332
333 48124214 inline DirectoryEntry()
334 96248428 : hardlink_group_(0)
335 48124214 , is_nested_catalog_root_(false)
336 48124214 , is_nested_catalog_mountpoint_(false)
337 48124214 , is_bind_mountpoint_(false)
338 48124214 , is_chunked_file_(false)
339 48124214 , is_hidden_(false)
340 48124214 , is_negative_(false) { }
341
342 3702 inline explicit DirectoryEntry(SpecialDirents special_type)
343 7404 : hardlink_group_(0)
344 3702 , is_nested_catalog_root_(false)
345 3702 , is_nested_catalog_mountpoint_(false)
346 3702 , is_bind_mountpoint_(false)
347 3702 , is_chunked_file_(false)
348 3702 , is_hidden_(false)
349 3702 , is_negative_(true) {
350
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3702 times.
3702 assert(special_type == kDirentNegative);
351 3702 }
352
353 420 inline SpecialDirents GetSpecial() const {
354
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 360 times.
420 return is_negative_ ? kDirentNegative : kDirentNormal;
355 }
356
357 Differences CompareTo(const DirectoryEntry &other) const;
358 inline bool operator==(const DirectoryEntry &other) const {
359 return CompareTo(other) == Difference::kIdentical;
360 }
361 inline bool operator!=(const DirectoryEntry &other) const {
362 return !(*this == other);
363 }
364
365 4008 inline bool IsNegative() const { return is_negative_; }
366 46506 inline bool IsNestedCatalogRoot() const { return is_nested_catalog_root_; }
367 41783 inline bool IsNestedCatalogMountpoint() const {
368 41783 return is_nested_catalog_mountpoint_;
369 }
370 31313 inline bool IsBindMountpoint() const { return is_bind_mountpoint_; }
371 63987 inline bool IsChunkedFile() const { return is_chunked_file_; }
372 39335 inline bool IsHidden() const { return is_hidden_; }
373 4018 inline uint32_t hardlink_group() const { return hardlink_group_; }
374
375 290 inline void set_hardlink_group(const uint32_t group) {
376 290 hardlink_group_ = group;
377 290 }
378 1872 inline void set_is_nested_catalog_mountpoint(const bool val) {
379 1872 is_nested_catalog_mountpoint_ = val;
380 1872 }
381 1714 inline void set_is_nested_catalog_root(const bool val) {
382 1714 is_nested_catalog_root_ = val;
383 1714 }
384 98 inline void set_is_bind_mountpoint(const bool val) {
385 98 is_bind_mountpoint_ = val;
386 98 }
387 5664 inline void set_is_chunked_file(const bool val) { is_chunked_file_ = val; }
388 98 inline void set_is_hidden(const bool val) { is_hidden_ = val; }
389
390 private:
391 /**
392 * Hardlink handling is emulated in CVMFS. Since inodes are allocated on
393 * demand we save hardlink relationships using the same hardlink_group.
394 */
395 uint32_t hardlink_group_;
396
397 // TODO(jblomer): transform into bitfield to save memory
398 bool is_nested_catalog_root_;
399 bool is_nested_catalog_mountpoint_;
400 bool is_bind_mountpoint_;
401 bool is_chunked_file_;
402 bool is_hidden_;
403 bool is_negative_;
404 };
405
406
407 /**
408 * Saves memory for large directory listings.
409 */
410 struct StatEntry {
411 NameString name;
412 struct stat info;
413
414 207 StatEntry() { memset(&info, 0, sizeof(info)); }
415 StatEntry(const NameString &n, const struct stat &i) : name(n), info(i) { }
416 };
417
418
419 typedef std::vector<DirectoryEntry> DirectoryEntryList;
420 typedef std::vector<DirectoryEntryBase> DirectoryEntryBaseList;
421 // TODO(jblomer): use mmap for large listings
422 typedef BigVector<StatEntry> StatEntryList;
423
424 } // namespace catalog
425
426 #endif // CVMFS_DIRECTORY_ENTRY_H_
427