GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/fs_traversal.h
Date: 2025-03-09 02:34:28
Exec Total Coverage
Lines: 92 99 92.9%
Branches: 127 244 52.0%

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 <errno.h>
12
13 #include <cassert>
14 #include <cstdlib>
15
16 #include <set>
17 #include <string>
18
19 #include "util/async.h"
20 #include "util/exception.h"
21 #include "util/logging.h"
22 #include "util/platform.h"
23
24 #ifdef CVMFS_NAMESPACE_GUARD
25 namespace CVMFS_NAMESPACE_GUARD {
26 #endif
27
28 /**
29 * @brief A simple recursion engine to abstract the recursion of directories.
30 * It provides several callback hooks to instrument and control the recursion.
31 * Hooks will be called on the provided delegate object of type T
32 *
33 * Callbacks are called for every directory entry found by the recursion engine.
34 * The recursion can be influenced by return values of these callbacks.
35 */
36 template <class T>
37 class FileSystemTraversal {
38 public:
39 typedef void (T::*VoidCallback)(const std::string &relative_path,
40 const std::string &dir_name);
41 typedef bool (T::*BoolCallback)(const std::string &relative_path,
42 const std::string &dir_name);
43
44
45 VoidCallback fn_enter_dir;
46 VoidCallback fn_leave_dir;
47 VoidCallback fn_new_file;
48 VoidCallback fn_new_symlink;
49 VoidCallback fn_new_socket;
50 VoidCallback fn_new_block_dev;
51 VoidCallback fn_new_character_dev;
52 VoidCallback fn_new_fifo;
53
54 /**
55 * Optional callback for all files during recursion to decide
56 * whether to completely ignore the file. If this callback returns
57 * true then the file will not be processed (this is a replacement
58 * for the ignored_files set, and it allows to ignore based on names
59 * or something else). If the function is not specified, no files
60 * will be ignored (except for "." and "..").
61 */
62 BoolCallback fn_ignore_file;
63
64 /**
65 * Callback if a directory was found. Depending on the response of
66 * the callback, the recursion will continue in the found directory/
67 * If this callback is not specified, it will recurse by default.
68 */
69 BoolCallback fn_new_dir_prefix;
70
71 /**
72 * Callback for a found directory after it was already recursed
73 * e.g. for deletion of directories: first delete content,
74 * then the directory itself
75 */
76 VoidCallback fn_new_dir_postfix;
77
78
79 /**
80 * Create a new recursion engine
81 * @param delegate The object that will receive the callbacks
82 * @param relative_to_directory The DirEntries will be created relative
83 * to this directory
84 * @param recurse Should the traversal engine recurse? (if not,
85 * it just traverses the given directory)
86 */
87 822 FileSystemTraversal(T *delegate,
88 const std::string &relative_to_directory,
89 const bool recurse) :
90 822 fn_enter_dir(NULL),
91 822 fn_leave_dir(NULL),
92 822 fn_new_file(NULL),
93 822 fn_new_symlink(NULL),
94 822 fn_new_socket(NULL),
95 822 fn_new_block_dev(NULL),
96 822 fn_new_character_dev(NULL),
97 822 fn_new_fifo(NULL),
98 822 fn_ignore_file(NULL),
99 822 fn_new_dir_prefix(NULL),
100 822 fn_new_dir_postfix(NULL),
101 822 delegate_(delegate),
102 822 relative_to_directory_(relative_to_directory),
103 822 recurse_(recurse)
104 {
105 822 Init();
106 822 }
107
108 /**
109 * Start the recursion.
110 * @param dir_path The directory to start the recursion at
111 */
112 822 void Recurse(const std::string &dir_path) const {
113
13/18
✓ Branch 0 taken 655 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 181 times.
✓ Branch 3 taken 474 times.
✓ Branch 4 taken 181 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✓ Branch 7 taken 157 times.
✓ Branch 8 taken 24 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 23 times.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 22 times.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 22 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
822 assert(fn_enter_dir != NULL ||
114 fn_leave_dir != NULL ||
115 fn_new_file != NULL ||
116 fn_new_symlink != NULL ||
117 fn_new_dir_prefix != NULL ||
118 fn_new_block_dev != NULL ||
119 fn_new_character_dev != NULL ||
120 fn_new_fifo != NULL ||
121 fn_new_socket != NULL);
122
123
6/12
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 653 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 6 times.
✓ Branch 12 taken 653 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
822 assert(relative_to_directory_.length() == 0 ||
124 dir_path.substr(0, relative_to_directory_.length()) ==
125 relative_to_directory_);
126
127
2/4
✓ Branch 2 taken 659 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 659 times.
✗ Branch 6 not taken.
822 DoRecursion(dir_path, "");
128 822 }
129
130 private:
131 // The delegate all hooks are called on
132 T *delegate_;
133
134 /** dir_path in callbacks will be relative to this directory */
135 std::string relative_to_directory_;
136 bool recurse_;
137
138
139 822 void Init() {
140 822 }
141
142 1914 void DoRecursion(const std::string &parent_path, const std::string &dir_name)
143 const
144 {
145 DIR *dip;
146 platform_dirent64 *dit;
147
7/13
✓ Branch 1 taken 990 times.
✓ Branch 2 taken 659 times.
✓ Branch 4 taken 990 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 659 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 1649 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 659 times.
✓ Branch 14 taken 990 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
3828 const std::string path = parent_path + ((!dir_name.empty()) ?
148 ("/" + dir_name) : "");
149
150 // Change into directory and notify the user
151
1/2
✓ Branch 4 taken 1649 times.
✗ Branch 5 not taken.
1914 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "entering %s (%s -- %s)",
152 path.c_str(), parent_path.c_str(), dir_name.c_str());
153
1/2
✓ Branch 2 taken 1649 times.
✗ Branch 3 not taken.
1914 dip = opendir(path.c_str());
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1649 times.
1914 if (!dip) {
155 PANIC(kLogStderr,
156 "Failed to open %s (%d).\n"
157 "Please check directory permissions.",
158 path.c_str(), errno);
159 }
160
1/2
✓ Branch 1 taken 1649 times.
✗ Branch 2 not taken.
1914 Notify(fn_enter_dir, parent_path, dir_name);
161
162 // Walk through the open directory notifying the user about contents
163
3/4
✓ Branch 1 taken 68317 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 66668 times.
✓ Branch 4 taken 1649 times.
70996 while ((dit = platform_readdir(dip)) != NULL) {
164 // Check if file should be ignored
165
14/32
✓ Branch 2 taken 66668 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 65019 times.
✓ Branch 6 taken 1649 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 65019 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 1649 times.
✓ Branch 13 taken 63370 times.
✓ Branch 14 taken 65019 times.
✓ Branch 15 taken 1649 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 65019 times.
✓ Branch 18 taken 1649 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 66668 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✓ Branch 23 taken 66668 times.
✗ Branch 24 not taken.
✓ Branch 26 taken 3298 times.
✓ Branch 27 taken 63370 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.
69082 if (std::string(dit->d_name) == "." || std::string(dit->d_name) == "..") {
166 3846 continue;
167
2/2
✓ Branch 0 taken 255 times.
✓ Branch 1 taken 63115 times.
65254 } else if (fn_ignore_file != NULL) {
168
4/6
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 255 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 246 times.
510 if (Notify(fn_ignore_file, path, dit->d_name)) {
169
1/3
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
18 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "ignoring %s/%s",
170 18 path.c_str(), dit->d_name);
171 18 continue;
172 }
173 } else {
174
1/3
✓ Branch 1 taken 63115 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
64744 LogCvmfs(kLogFsTraversal, kLogVerboseMsg,
175 "not ignoring %s/%s (fn_ignore_file not set)",
176 64744 path.c_str(), dit->d_name);
177 }
178
179 // Notify user about found directory entry
180 platform_stat64 info;
181
2/4
✓ Branch 1 taken 63361 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63361 times.
✗ Branch 5 not taken.
65236 int retval = platform_lstat((path + "/" + dit->d_name).c_str(), &info);
182
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63361 times.
65236 if (retval != 0) {
183 PANIC(kLogStderr, "failed to lstat '%s' errno: %d",
184 (path + "/" + dit->d_name).c_str(), errno);
185 }
186
2/2
✓ Branch 0 taken 58922 times.
✓ Branch 1 taken 4439 times.
65236 if (S_ISDIR(info.st_mode)) {
187
1/3
✓ Branch 1 taken 58922 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
59053 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing directory %s/%s",
188 59053 path.c_str(), dit->d_name);
189
10/20
✓ Branch 2 taken 58922 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 58922 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 6717 times.
✓ Branch 8 taken 52205 times.
✓ Branch 9 taken 990 times.
✓ Branch 10 taken 5727 times.
✓ Branch 11 taken 58922 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 58922 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 990 times.
✓ Branch 18 taken 57932 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
59053 if (Notify(fn_new_dir_prefix, path, dit->d_name) && recurse_) {
190
2/4
✓ Branch 2 taken 990 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 990 times.
✗ Branch 6 not taken.
1092 DoRecursion(path, dit->d_name);
191 }
192
2/4
✓ Branch 2 taken 58922 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 58922 times.
✗ Branch 6 not taken.
59053 Notify(fn_new_dir_postfix, path, dit->d_name);
193
2/2
✓ Branch 0 taken 2731 times.
✓ Branch 1 taken 1708 times.
6183 } else if (S_ISREG(info.st_mode)) {
194
1/3
✓ Branch 1 taken 2731 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2781 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing regular file %s/%s",
195 2781 path.c_str(), dit->d_name);
196
2/4
✓ Branch 2 taken 2731 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2731 times.
✗ Branch 6 not taken.
2781 Notify(fn_new_file, path, dit->d_name);
197
2/2
✓ Branch 0 taken 1514 times.
✓ Branch 1 taken 194 times.
3402 } else if (S_ISLNK(info.st_mode)) {
198
1/3
✓ Branch 1 taken 1514 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3020 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing symlink %s/%s",
199 3020 path.c_str(), dit->d_name);
200
2/4
✓ Branch 2 taken 1514 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1514 times.
✗ Branch 6 not taken.
3020 Notify(fn_new_symlink, path, dit->d_name);
201
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 169 times.
382 } else if (S_ISSOCK(info.st_mode)) {
202
1/3
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
44 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing socket %s/%s",
203 44 path.c_str(), dit->d_name);
204
2/4
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
44 Notify(fn_new_socket, path, dit->d_name);
205
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 157 times.
338 } else if (S_ISBLK(info.st_mode)) {
206
1/3
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
24 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing block-device %s/%s",
207 24 path.c_str(), dit->d_name);
208
2/4
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
24 Notify(fn_new_block_dev, path, dit->d_name);
209
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 21 times.
314 } else if (S_ISCHR(info.st_mode)) {
210
1/3
✓ Branch 1 taken 136 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
272 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing character-device "
211 "%s/%s",
212 272 path.c_str(), dit->d_name);
213
2/4
✓ Branch 2 taken 136 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 136 times.
✗ Branch 6 not taken.
272 Notify(fn_new_character_dev, path, dit->d_name);
214
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
42 } else if (S_ISFIFO(info.st_mode)) {
215
1/3
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
42 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing FIFO %s/%s",
216 42 path.c_str(), dit->d_name);
217
2/4
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
42 Notify(fn_new_fifo, path, dit->d_name);
218 } else {
219 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "unknown file type %s/%s",
220 path.c_str(), dit->d_name);
221 }
222 }
223
224 // Close directory and notify user
225
1/2
✓ Branch 1 taken 1649 times.
✗ Branch 2 not taken.
1914 closedir(dip);
226
1/2
✓ Branch 2 taken 1649 times.
✗ Branch 3 not taken.
1914 LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "leaving %s", path.c_str());
227
1/2
✓ Branch 1 taken 1649 times.
✗ Branch 2 not taken.
1914 Notify(fn_leave_dir, parent_path, dir_name);
228 1914 }
229
230 59563 inline bool Notify(const BoolCallback callback,
231 const std::string &parent_path,
232 const std::string &entry_name) const
233 {
234
2/2
✓ Branch 0 taken 53453 times.
✓ Branch 1 taken 5724 times.
113378 return (callback == NULL) ? true :
235
6/11
✓ Branch 0 taken 107 times.
✓ Branch 1 taken 53346 times.
✓ Branch 3 taken 53453 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 53453 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1002 times.
✓ Branch 9 taken 52451 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
113378 (delegate_->*callback)(GetRelativePath(parent_path),
236
2/2
✓ Branch 0 taken 53453 times.
✓ Branch 1 taken 5724 times.
119126 entry_name);
237 }
238
239 69064 inline void Notify(const VoidCallback callback,
240 const std::string &parent_path,
241 const std::string &entry_name) const
242 {
243
2/2
✓ Branch 0 taken 5988 times.
✓ Branch 1 taken 60671 times.
69064 if (callback != NULL) {
244
3/4
✓ Branch 0 taken 417 times.
✓ Branch 1 taken 5571 times.
✓ Branch 4 taken 5988 times.
✗ Branch 5 not taken.
7965 (delegate_->*callback)(GetRelativePath(parent_path),
245 entry_name);
246 }
247 69064 }
248
249 59441 std::string GetRelativePath(const std::string &absolute_path) const {
250 59441 const unsigned int rel_dir_len = relative_to_directory_.length();
251
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 57102 times.
59441 if (rel_dir_len >= absolute_path.length()) {
252
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
311 return "";
253
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 57102 times.
59130 } else if (rel_dir_len > 1) {
254 542 return absolute_path.substr(rel_dir_len + 1);
255
1/2
✓ Branch 0 taken 57102 times.
✗ Branch 1 not taken.
58588 } else if (rel_dir_len == 0) {
256 58588 return absolute_path;
257 } else if (relative_to_directory_ == "/") {
258 return absolute_path.substr(1);
259 }
260
261 return "";
262 }
263 }; // FileSystemTraversal
264
265 #ifdef CVMFS_NAMESPACE_GUARD
266 } // namespace CVMFS_NAMESPACE_GUARD
267 #endif
268
269 #endif // CVMFS_UTIL_FS_TRAVERSAL_H_
270