GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/directory_entry.h
Date: 2026-06-28 02:36:10
Exec Total Coverage
Lines: 126 138 91.3%
Branches: 22 38 57.9%

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