GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/fs_traversal.h
Date: 2025-12-21 02:39:23
Exec Total Coverage
Lines: 92 99 92.9%
Branches: 127 245 51.8%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * It provides a file system traversal framework to abstract the traversal
5 * of directories.
6 */
7
8 #ifndef CVMFS_UTIL_FS_TRAVERSAL_H_
9 #define CVMFS_UTIL_FS_TRAVERSAL_H_
10
11 #include <dirent.h>
12 #include <errno.h>
13 #include <sys/stat.h>
14
15 #include <cassert>
16 #include <cstdlib>
17 #include <string>
18
19 #include "util/exception.h"
20 #include "util/logging.h"
21 #include "util/platform.h"
22
23 #ifdef CVMFS_NAMESPACE_GUARD
24 namespace CVMFS_NAMESPACE_GUARD {
25 #endif
26
27 /**
28 * @brief A simple recursion engine to abstract the recursion of directories.
29 * It provides several callback hooks to instrument and control the recursion.
30 * Hooks will be called on the provided delegate object of type T
31 *
32 * Callbacks are called for every directory entry found by the recursion engine.
33 * The recursion can be influenced by return values of these callbacks.
34 */
35 template<class T>
36 class FileSystemTraversal {
37 public:
38 typedef void (T::*VoidCallback)(const std::string &relative_path,
39 const std::string &dir_name);
40 typedef bool (T::*BoolCallback)(const std::string &relative_path,
41 const std::string &dir_name);
42
43
44 VoidCallback fn_enter_dir;
45 VoidCallback fn_leave_dir;
46 VoidCallback fn_new_file;
47 VoidCallback fn_new_symlink;
48 VoidCallback fn_new_socket;
49 VoidCallback fn_new_block_dev;
50 VoidCallback fn_new_character_dev;
51 VoidCallback fn_new_fifo;
52
53 /**
54 * Optional callback for all files during recursion to decide
55 * whether to completely ignore the file. If this callback returns
56 * true then the file will not be processed (this is a replacement
57 * for the ignored_files set, and it allows to ignore based on names
58 * or something else). If the function is not specified, no files
59 * will be ignored (except for "." and "..").
60 */
61 BoolCallback fn_ignore_file;
62
63 /**
64 * Callback if a directory was found. Depending on the response of
65 * the callback, the recursion will continue in the found directory/
66 * If this callback is not specified, it will recurse by default.
67 */
68 BoolCallback fn_new_dir_prefix;
69
70 /**
71 * Callback for a found directory after it was already recursed
72 * e.g. for deletion of directories: first delete content,
73 * then the directory itself
74 */
75 VoidCallback fn_new_dir_postfix;
76
77
78 /**
79 * Create a new recursion engine
80 * @param delegate The object that will receive the callbacks
81 * @param relative_to_directory The DirEntries will be created relative
82 * to this directory
83 * @param recurse Should the traversal engine recurse? (if not,
84 * it just traverses the given directory)
85 */
86 20257 FileSystemTraversal(T *delegate,
87 const std::string &relative_to_directory,
88 const bool recurse)
89 20257 : fn_enter_dir(NULL)
90 20257 , fn_leave_dir(NULL)
91 20257 , fn_new_file(NULL)
92 20257 , fn_new_symlink(NULL)
93 20257 , fn_new_socket(NULL)
94 20257 , fn_new_block_dev(NULL)
95 20257 , fn_new_character_dev(NULL)
96 20257 , fn_new_fifo(NULL)
97 20257 , fn_ignore_file(NULL)
98 20257 , fn_new_dir_prefix(NULL)
99 20257 , fn_new_dir_postfix(NULL)
100 20257 , delegate_(delegate)
101 20257 , relative_to_directory_(relative_to_directory)
102 20257 , recurse_(recurse) {
103 20257 Init();
104 20257 }
105
106 /**
107 * Start the recursion.
108 * @param dir_path The directory to start the recursion at
109 */
110 20257 void Recurse(const std::string &dir_path) const {
111
13/18
✓ Branch 0 taken 16577 times.
✓ Branch 1 taken 180 times.
✓ Branch 2 taken 3804 times.
✓ Branch 3 taken 12773 times.
✓ Branch 4 taken 3804 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 574 times.
✓ Branch 7 taken 3230 times.
✓ Branch 8 taken 574 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 529 times.
✓ Branch 11 taken 45 times.
✓ Branch 12 taken 484 times.
✓ Branch 13 taken 45 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 484 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
20257 assert(fn_enter_dir != NULL || fn_leave_dir != NULL || fn_new_file != NULL
112 || fn_new_symlink != NULL || fn_new_dir_prefix != NULL
113 || fn_new_block_dev != NULL || fn_new_character_dev != NULL
114 || fn_new_fifo != NULL || fn_new_socket != NULL);
115
116
6/12
✓ Branch 1 taken 270 times.
✓ Branch 2 taken 16487 times.
✓ Branch 5 taken 270 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 270 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 270 times.
✓ Branch 12 taken 16487 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
20257 assert(relative_to_directory_.length() == 0
117 || dir_path.substr(0, relative_to_directory_.length())
118 == relative_to_directory_);
119
120
2/4
✓ Branch 2 taken 16757 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 16757 times.
✗ Branch 6 not taken.
20257 DoRecursion(dir_path, "");
121 20257 }
122
123 private:
124 // The delegate all hooks are called on
125 T *delegate_;
126
127 /** dir_path in callbacks will be relative to this directory */
128 std::string relative_to_directory_;
129 bool recurse_;
130
131
132 20257 void Init() { }
133
134 47935 void DoRecursion(const std::string &parent_path,
135 const std::string &dir_name) const {
136 DIR *dip;
137 platform_dirent64 *dit;
138
1/2
✓ Branch 1 taken 39845 times.
✗ Branch 2 not taken.
47935 const std::string path = parent_path
139
6/12
✓ Branch 1 taken 23088 times.
✓ Branch 2 taken 16757 times.
✓ Branch 4 taken 23088 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 16757 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 16757 times.
✓ Branch 11 taken 23088 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
95870 + ((!dir_name.empty()) ? ("/" + dir_name) : "");
140
141 // Change into directory and notify the user
142
1/2
✓ Branch 4 taken 39845 times.
✗ Branch 5 not taken.
47935 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "entering %s (%s -- %s)",
143 path.c_str(), parent_path.c_str(), dir_name.c_str());
144
1/2
✓ Branch 2 taken 39845 times.
✗ Branch 3 not taken.
47935 dip = opendir(path.c_str());
145
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39845 times.
47935 if (!dip) {
146 PANIC(kLogStderr,
147 "Failed to open %s (%d).\n"
148 "Please check directory permissions.",
149 path.c_str(), errno);
150 }
151
1/2
✓ Branch 1 taken 39845 times.
✗ Branch 2 not taken.
47935 Notify(fn_enter_dir, parent_path, dir_name);
152
153 // Walk through the open directory notifying the user about contents
154
3/4
✓ Branch 1 taken 1495662 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1455817 times.
✓ Branch 4 taken 39845 times.
1587824 while ((dit = platform_readdir(dip)) != NULL) {
155 // Check if file should be ignored
156
14/32
✓ Branch 2 taken 1455817 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1415972 times.
✓ Branch 6 taken 39845 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1415972 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 39845 times.
✓ Branch 13 taken 1376127 times.
✓ Branch 14 taken 1415972 times.
✓ Branch 15 taken 39845 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 1415972 times.
✓ Branch 18 taken 39845 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 1455817 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✓ Branch 23 taken 1455817 times.
✗ Branch 24 not taken.
✓ Branch 26 taken 79690 times.
✓ Branch 27 taken 1376127 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
1539889 if (std::string(dit->d_name) == "." || std::string(dit->d_name) == "..") {
157 96680 continue;
158
2/2
✓ Branch 0 taken 11475 times.
✓ Branch 1 taken 1364652 times.
1444019 } else if (fn_ignore_file != NULL) {
159
4/6
✓ Branch 2 taken 11475 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 11475 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 405 times.
✓ Branch 10 taken 11070 times.
22950 if (Notify(fn_ignore_file, path, dit->d_name)) {
160
1/3
✓ Branch 1 taken 405 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
810 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "ignoring %s/%s",
161 810 path.c_str(), dit->d_name);
162 810 continue;
163 }
164 } else {
165
1/3
✓ Branch 1 taken 1364652 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1421069 LogCvmfs(kLogFsTraversal, kLogVerboseMsg,
166 "not ignoring %s/%s (fn_ignore_file not set)", path.c_str(),
167 1421069 dit->d_name);
168 }
169
170 // Notify user about found directory entry
171 platform_stat64 info;
172
2/4
✓ Branch 1 taken 1375722 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1375722 times.
✗ Branch 5 not taken.
1443209 const int retval = platform_lstat((path + "/" + dit->d_name).c_str(),
173 &info);
174
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1375722 times.
1443209 if (retval != 0) {
175 PANIC(kLogStderr, "failed to lstat '%s' errno: %d",
176 (path + "/" + dit->d_name).c_str(), errno);
177 }
178
2/2
✓ Branch 0 taken 1245470 times.
✓ Branch 1 taken 130252 times.
1443209 if (S_ISDIR(info.st_mode)) {
179
1/3
✓ Branch 1 taken 1245470 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1251365 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing directory %s/%s",
180 1251365 path.c_str(), dit->d_name);
181
10/20
✓ Branch 2 taken 1245470 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1245470 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 149703 times.
✓ Branch 8 taken 1095767 times.
✓ Branch 9 taken 23088 times.
✓ Branch 10 taken 126615 times.
✓ Branch 11 taken 1245470 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 1245470 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 23088 times.
✓ Branch 18 taken 1222382 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
1251365 if (Notify(fn_new_dir_prefix, path, dit->d_name) && recurse_) {
182
2/4
✓ Branch 2 taken 23088 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 23088 times.
✗ Branch 6 not taken.
27678 DoRecursion(path, dit->d_name);
183 }
184
2/4
✓ Branch 2 taken 1245470 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1245470 times.
✗ Branch 6 not taken.
1251365 Notify(fn_new_dir_postfix, path, dit->d_name);
185
2/2
✓ Branch 0 taken 70562 times.
✓ Branch 1 taken 59690 times.
191844 } else if (S_ISREG(info.st_mode)) {
186
1/3
✓ Branch 1 taken 70562 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
72812 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing regular file %s/%s",
187 72812 path.c_str(), dit->d_name);
188
2/4
✓ Branch 2 taken 70562 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 70562 times.
✗ Branch 6 not taken.
72812 Notify(fn_new_file, path, dit->d_name);
189
2/2
✓ Branch 0 taken 51047 times.
✓ Branch 1 taken 8643 times.
119032 } else if (S_ISLNK(info.st_mode)) {
190
1/3
✓ Branch 1 taken 51047 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
101929 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing symlink %s/%s",
191 101929 path.c_str(), dit->d_name);
192
2/4
✓ Branch 2 taken 51047 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 51047 times.
✗ Branch 6 not taken.
101929 Notify(fn_new_symlink, path, dit->d_name);
193
2/2
✓ Branch 0 taken 1038 times.
✓ Branch 1 taken 7605 times.
17103 } else if (S_ISSOCK(info.st_mode)) {
194
1/3
✓ Branch 1 taken 1038 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1893 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing socket %s/%s",
195 1893 path.c_str(), dit->d_name);
196
2/4
✓ Branch 2 taken 1038 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1038 times.
✗ Branch 6 not taken.
1893 Notify(fn_new_socket, path, dit->d_name);
197
2/2
✓ Branch 0 taken 540 times.
✓ Branch 1 taken 7065 times.
15210 } else if (S_ISBLK(info.st_mode)) {
198
1/3
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1080 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing block-device %s/%s",
199 1080 path.c_str(), dit->d_name);
200
2/4
✓ Branch 2 taken 540 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 540 times.
✗ Branch 6 not taken.
1080 Notify(fn_new_block_dev, path, dit->d_name);
201
2/2
✓ Branch 0 taken 6120 times.
✓ Branch 1 taken 945 times.
14130 } else if (S_ISCHR(info.st_mode)) {
202
1/3
✓ Branch 1 taken 6120 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12240 LogCvmfs(kLogFsTraversal, kLogVerboseMsg,
203 "passing character-device "
204 "%s/%s",
205 12240 path.c_str(), dit->d_name);
206
2/4
✓ Branch 2 taken 6120 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6120 times.
✗ Branch 6 not taken.
12240 Notify(fn_new_character_dev, path, dit->d_name);
207
1/2
✓ Branch 0 taken 945 times.
✗ Branch 1 not taken.
1890 } else if (S_ISFIFO(info.st_mode)) {
208
1/3
✓ Branch 1 taken 945 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1890 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing FIFO %s/%s",
209 1890 path.c_str(), dit->d_name);
210
2/4
✓ Branch 2 taken 945 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 945 times.
✗ Branch 6 not taken.
1890 Notify(fn_new_fifo, path, dit->d_name);
211 } else {
212 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "unknown file type %s/%s",
213 path.c_str(), dit->d_name);
214 }
215 }
216
217 // Close directory and notify user
218
1/2
✓ Branch 1 taken 39845 times.
✗ Branch 2 not taken.
47935 closedir(dip);
219
1/2
✓ Branch 2 taken 39845 times.
✗ Branch 3 not taken.
47935 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "leaving %s", path.c_str());
220
1/2
✓ Branch 1 taken 39845 times.
✗ Branch 2 not taken.
47935 Notify(fn_leave_dir, parent_path, dir_name);
221 47935 }
222
223 1274315 inline bool Notify(const BoolCallback callback,
224 const std::string &parent_path,
225 const std::string &entry_name) const {
226
2/2
✓ Branch 0 taken 1130465 times.
✓ Branch 1 taken 126480 times.
2421070 return (callback == NULL) ? true
227
6/11
✓ Branch 0 taken 4815 times.
✓ Branch 1 taken 1125650 times.
✓ Branch 3 taken 1130465 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1130465 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 23628 times.
✓ Branch 9 taken 1106837 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
2421070 : (delegate_->*callback)(
228
2/2
✓ Branch 0 taken 1130465 times.
✓ Branch 1 taken 126480 times.
2548630 GetRelativePath(parent_path), entry_name);
229 }
230
231 1539079 inline void Notify(const VoidCallback callback,
232 const std::string &parent_path,
233 const std::string &entry_name) const {
234
2/2
✓ Branch 0 taken 170996 times.
✓ Branch 1 taken 1284416 times.
1539079 if (callback != NULL) {
235
3/4
✓ Branch 0 taken 18765 times.
✓ Branch 1 taken 152231 times.
✓ Branch 4 taken 170996 times.
✗ Branch 5 not taken.
243073 (delegate_->*callback)(GetRelativePath(parent_path), entry_name);
236 }
237 1539079 }
238
239 1301461 std::string GetRelativePath(const std::string &absolute_path) const {
240 1301461 const unsigned int rel_dir_len = relative_to_directory_.length();
241
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1213094 times.
1301461 if (rel_dir_len >= absolute_path.length()) {
242
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
13995 return "";
243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1213094 times.
1287466 } else if (rel_dir_len > 1) {
244 24390 return absolute_path.substr(rel_dir_len + 1);
245
1/2
✓ Branch 0 taken 1213094 times.
✗ Branch 1 not taken.
1263076 } else if (rel_dir_len == 0) {
246 1263076 return absolute_path;
247 } else if (relative_to_directory_ == "/") {
248 return absolute_path.substr(1);
249 }
250
251 return "";
252 }
253 }; // FileSystemTraversal
254
255 #ifdef CVMFS_NAMESPACE_GUARD
256 } // namespace CVMFS_NAMESPACE_GUARD
257 #endif
258
259 #endif // CVMFS_UTIL_FS_TRAVERSAL_H_
260