GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/directory_entry.h
Date: 2026-10-04 02:40:33
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 17946281 inline DirectoryEntryBase()
119 17946281 : inode_(kInvalidInode)
120 17946281 , mode_(0)
121 17946281 , uid_(0)
122 17946281 , gid_(0)
123 17946281 , size_(0)
124 17946281 , mtime_(0)
125 17946281 , mtime_ns_(-1)
126 17946281 , linkcount_(1) // generally a normal file has linkcount 1 -> default
127 17946281 , has_xattrs_(false)
128 17946281 , is_external_file_(false)
129 17946281 , is_direct_io_(false)
130 17946281 , is_bundle_trigger_(false)
131 17946281 , is_volatile_(false)
132
1/2
✓ Branch 3 taken 17946281 times.
✗ Branch 4 not taken.
17946281 , compression_algorithm_(zlib::kZlibDefault) { }
133
134 28684 inline bool IsRegular() const { return S_ISREG(mode_); }
135 31237 inline bool IsLink() const { return S_ISLNK(mode_); }
136 30455 inline bool IsDirectory() const { return S_ISDIR(mode_); }
137 11385 inline bool IsFifo() const { return S_ISFIFO(mode_); }
138 11369 inline bool IsSocket() const { return S_ISSOCK(mode_); }
139 29083 inline bool IsCharDev() const { return S_ISCHR(mode_); }
140 29083 inline bool IsBlockDev() const { return S_ISBLK(mode_); }
141 11385 inline bool IsSpecial() const {
142
5/8
✓ Branch 1 taken 11369 times.
✓ Branch 2 taken 16 times.
✓ Branch 4 taken 11369 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 11369 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 11369 times.
11385 return IsFifo() || IsSocket() || IsCharDev() || IsBlockDev();
143 }
144 15947 inline bool IsExternalFile() const { return is_external_file_; }
145 7027 inline bool IsDirectIo() const { return is_direct_io_; }
146 7058 inline bool IsBundleTrigger() const { return is_bundle_trigger_; }
147 7027 inline bool IsVolatile() const { return is_volatile_; }
148 18731 inline bool HasXattrs() const { return has_xattrs_; }
149 18115 inline bool HasMtimeNs() const { return mtime_ns_ >= 0; }
150
151 4175 inline inode_t inode() const { return inode_; }
152 5888 inline uint32_t linkcount() const { return linkcount_; }
153 38032 inline NameString name() const { return name_; }
154 742 inline LinkString symlink() const { return symlink_; }
155 588 inline time_t mtime() const { return mtime_; }
156 492 inline int32_t mtime_ns() const { return mtime_ns_; }
157 588 inline unsigned int mode() const { return mode_; }
158 2044 inline uid_t uid() const { return uid_; }
159 2044 inline gid_t gid() const { return gid_; }
160 4641 inline shash::Any checksum() const { return checksum_; }
161 23020 inline const shash::Any *checksum_ptr() const { return &checksum_; }
162 18 inline shash::Algorithms hash_algorithm() const {
163 18 return checksum_.algorithm;
164 }
165 16318 inline uint64_t size() const {
166
2/2
✓ Branch 1 taken 128 times.
✓ Branch 2 taken 16190 times.
16318 if (IsLink())
167 128 return symlink().GetLength();
168
3/6
✓ Branch 1 taken 16190 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16190 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 16190 times.
16190 if (IsBlockDev() || IsCharDev())
169 ✗ return 0;
170 16190 return size_;
171 }
172 1524 inline dev_t rdev() const {
173
3/6
✓ Branch 1 taken 1524 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1524 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1524 times.
1524 if (IsBlockDev() || IsCharDev())
174 ✗ return size_;
175 1524 return 1;
176 }
177 13827 inline std::string GetFullPath(const std::string &parent_directory) const {
178 13827 std::string file_path = parent_directory + "/";
179
3/6
✓ Branch 1 taken 13827 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 13827 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 13827 times.
✗ Branch 10 not taken.
13827 file_path.append(name().GetChars(), name().GetLength());
180 13827 return file_path;
181 }
182
183 526 inline void set_inode(const inode_t inode) { inode_ = inode; }
184 6458 inline void set_linkcount(const uint32_t linkcount) {
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6458 times.
6458 assert(linkcount > 0);
186 6458 linkcount_ = linkcount;
187 6458 }
188 ✗ inline void set_symlink(const LinkString &symlink) { symlink_ = symlink; }
189 10557 inline void set_has_xattrs(const bool has_xattrs) {
190 10557 has_xattrs_ = has_xattrs;
191 10557 }
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 12 inline void SetImplicitDirectoryMetadata() {
197 12 mode_ = S_IFDIR | 0755;
198 12 uid_ = 0;
199 12 gid_ = 0;
200 12 size_ = 0;
201 12 mtime_ = 0;
202 12 mtime_ns_ = -1;
203
3/6
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
12 symlink_ = LinkString("");
204 12 has_xattrs_ = false;
205 12 checksum_ = shash::Any();
206 12 compression_algorithm_ = zlib::kZlibDefault;
207 12 is_external_file_ = false;
208 12 is_direct_io_ = false;
209 12 is_bundle_trigger_ = false;
210 12 }
211
212 6619 inline zlib::Algorithms compression_algorithm() const {
213 6619 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 1428 inline struct stat GetStatStructure() const {
221 struct stat s;
222 1428 memset(&s, 0, sizeof(s));
223 1428 s.st_dev = 1;
224 1428 s.st_ino = inode_;
225 1428 s.st_mode = mode_;
226 1428 s.st_nlink = linkcount();
227 1428 s.st_uid = uid();
228 1428 s.st_gid = gid();
229 1428 s.st_rdev = rdev();
230 1428 s.st_size = static_cast<off_t>(size());
231 1428 s.st_blksize = 4096; // will be ignored by Fuse
232 1428 s.st_blocks = static_cast<blkcnt_t>(1 + size() / 512);
233 1428 s.st_atime = mtime_;
234 1428 s.st_mtime = mtime_;
235 1428 s.st_ctime = mtime_;
236
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1428 times.
1428 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 1428 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 6883 inline explicit DirectoryEntry(const DirectoryEntryBase &base)
324 6883 : DirectoryEntryBase(base)
325 6883 , hardlink_group_(0)
326 6883 , is_nested_catalog_root_(false)
327 6883 , is_nested_catalog_mountpoint_(false)
328 6883 , is_bind_mountpoint_(false)
329 6883 , is_chunked_file_(false)
330 6883 , is_hidden_(false)
331 6883 , is_negative_(false) { }
332
333 17944198 inline DirectoryEntry()
334 35888396 : hardlink_group_(0)
335 17944198 , is_nested_catalog_root_(false)
336 17944198 , is_nested_catalog_mountpoint_(false)
337 17944198 , is_bind_mountpoint_(false)
338 17944198 , is_chunked_file_(false)
339 17944198 , is_hidden_(false)
340 17944198 , is_negative_(false) { }
341
342 2059 inline explicit DirectoryEntry(SpecialDirents special_type)
343 4118 : hardlink_group_(0)
344 2059 , is_nested_catalog_root_(false)
345 2059 , is_nested_catalog_mountpoint_(false)
346 2059 , is_bind_mountpoint_(false)
347 2059 , is_chunked_file_(false)
348 2059 , is_hidden_(false)
349 2059 , is_negative_(true) {
350
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2059 times.
2059 assert(special_type == kDirentNegative);
351 2059 }
352
353 679 inline SpecialDirents GetSpecial() const {
354
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 582 times.
679 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 1492 inline bool IsNegative() const { return is_negative_; }
366 25659 inline bool IsNestedCatalogRoot() const { return is_nested_catalog_root_; }
367 20753 inline bool IsNestedCatalogMountpoint() const {
368 20753 return is_nested_catalog_mountpoint_;
369 }
370 15654 inline bool IsBindMountpoint() const { return is_bind_mountpoint_; }
371 35481 inline bool IsChunkedFile() const { return is_chunked_file_; }
372 18087 inline bool IsHidden() const { return is_hidden_; }
373 492 inline uint32_t hardlink_group() const { return hardlink_group_; }
374
375 130 inline void set_hardlink_group(const uint32_t group) {
376 130 hardlink_group_ = group;
377 130 }
378 984 inline void set_is_nested_catalog_mountpoint(const bool val) {
379 984 is_nested_catalog_mountpoint_ = val;
380 984 }
381 946 inline void set_is_nested_catalog_root(const bool val) {
382 946 is_nested_catalog_root_ = val;
383 946 }
384 12 inline void set_is_bind_mountpoint(const bool val) {
385 12 is_bind_mountpoint_ = val;
386 12 }
387 616 inline void set_is_chunked_file(const bool val) { is_chunked_file_ = val; }
388 12 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 110 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