GCC Code Coverage Report


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