GCC Code Coverage Report


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