GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
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_FS_TRAVERSAL_H_ |
||
9 |
#define CVMFS_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 "logging.h" |
||
20 |
#include "platform.h" |
||
21 |
#include "util/async.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 |
918 |
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 |
918 |
FileSystemTraversal(T *delegate, |
|
87 |
const std::string &relative_to_directory, |
||
88 |
const bool recurse) : |
||
89 |
fn_enter_dir(NULL), |
||
90 |
fn_leave_dir(NULL), |
||
91 |
fn_new_file(NULL), |
||
92 |
fn_new_symlink(NULL), |
||
93 |
fn_new_socket(NULL), |
||
94 |
fn_new_block_dev(NULL), |
||
95 |
fn_new_character_dev(NULL), |
||
96 |
fn_new_fifo(NULL), |
||
97 |
fn_ignore_file(NULL), |
||
98 |
fn_new_dir_prefix(NULL), |
||
99 |
fn_new_dir_postfix(NULL), |
||
100 |
delegate_(delegate), |
||
101 |
relative_to_directory_(relative_to_directory), |
||
102 |
918 |
recurse_(recurse) |
|
103 |
{ |
||
104 |
918 |
Init(); |
|
105 |
918 |
} |
|
106 |
|||
107 |
/** |
||
108 |
* Start the recursion. |
||
109 |
* @param dir_path The directory to start the recursion at |
||
110 |
*/ |
||
111 |
918 |
void Recurse(const std::string &dir_path) const { |
|
112 |
✓✗✓✓ ✓✗✓✗ ✓✗✓✓ ✓✓✗✓ ✗✗✗✓ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✓✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✓ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✓✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗ |
918 |
assert(fn_enter_dir != NULL || |
113 |
fn_leave_dir != NULL || |
||
114 |
fn_new_file != NULL || |
||
115 |
fn_new_symlink != NULL || |
||
116 |
fn_new_dir_prefix != NULL || |
||
117 |
fn_new_block_dev != NULL || |
||
118 |
fn_new_character_dev != NULL || |
||
119 |
fn_new_fifo != NULL || |
||
120 |
fn_new_socket != NULL); |
||
121 |
|||
122 |
✓✓✗✓ ✗✗✓✓ ✓✗✗✓ ✗✗✓✗ ✓✗✗✓ ✗✗✓✗ ✓✗✗✓ ✗✗✓✗ ✓✗✗✓ ✗✗✓✗ |
918 |
assert(relative_to_directory_.length() == 0 || |
123 |
dir_path.substr(0, relative_to_directory_.length()) == |
||
124 |
relative_to_directory_); |
||
125 |
|||
126 |
918 |
DoRecursion(dir_path, ""); |
|
127 |
918 |
} |
|
128 |
|||
129 |
private: |
||
130 |
// The delegate all hooks are called on |
||
131 |
T *delegate_; |
||
132 |
|||
133 |
/** dir_path in callbacks will be relative to this directory */ |
||
134 |
std::string relative_to_directory_; |
||
135 |
bool recurse_; |
||
136 |
|||
137 |
|||
138 |
918 |
void Init() { |
|
139 |
918 |
} |
|
140 |
|||
141 |
3172 |
void DoRecursion(const std::string &parent_path, const std::string &dir_name) |
|
142 |
const |
||
143 |
{ |
||
144 |
DIR *dip; |
||
145 |
platform_dirent64 *dit; |
||
146 |
const std::string path = parent_path + ((!dir_name.empty()) ? |
||
147 |
✓✓✗✗ ✗✗✓✓ ✓✓✗✗ ✓✓✓✓ ✗✗✓✓ ✗✓✗✗ ✓✗✓✓ ✗✗✓✓ |
3172 |
("/" + dir_name) : ""); |
148 |
|||
149 |
// Change into directory and notify the user |
||
150 |
3172 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "entering %s (%s -- %s)", |
|
151 |
path.c_str(), parent_path.c_str(), dir_name.c_str()); |
||
152 |
3172 |
dip = opendir(path.c_str()); |
|
153 |
✗✓✗✓ ✗✓✗✓ ✗✓ |
3172 |
if (!dip) { |
154 |
LogCvmfs(kLogFsTraversal, kLogStderr, "Failed to open %s (%d).\n" |
||
155 |
"Please check directory permissions.", |
||
156 |
path.c_str(), errno); |
||
157 |
abort(); |
||
158 |
} |
||
159 |
3172 |
Notify(fn_enter_dir, parent_path, dir_name); |
|
160 |
|||
161 |
// Walk through the open directory notifying the user about contents |
||
162 |
✓✓✓✓ ✓✓✓✓ ✓✓ |
114623 |
while ((dit = platform_readdir(dip)) != NULL) { |
163 |
// Check if file should be ignored |
||
164 |
✓✓✓✓ ✓✓✗✗ ✗✗✗✗ ✗✗✓✓ ✗✗✗✓ ✗✗✗✓ ✗✓✓✓ ✓✓✓✓ ✓✗✗✗ ✗✓✓✗ ✗✓✗✗ ✗✓✗✓ ✓✓✓✓ ✓✓✓✗ ✗✗✗✓ ✓✗✗✓ ✗✗✗✓ ✗✓✓✓ ✓✓✓✓ ✓✗✗✗ ✗✓✓✗ ✗✓✗✗ ✗✓✗✓ ✓✓✓✓ ✓✓✓✗ ✗✗✗✓ ✓✗✗✓ ✗✗✗✓ ✗✓✓ |
108279 |
if (std::string(dit->d_name) == "." || std::string(dit->d_name) == "..") { |
165 |
6344 |
continue; |
|
166 |
✓✓✗✓ ✓✗✗✓ ✗✓ |
101935 |
} else if (fn_ignore_file != NULL) { |
167 |
✗✗✗✗ ✗✓✗✗ ✗✗✓✓ |
461 |
if (Notify(fn_ignore_file, path, dit->d_name)) { |
168 |
9 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "ignoring %s/%s", |
|
169 |
path.c_str(), dit->d_name); |
||
170 |
9 |
continue; |
|
171 |
} |
||
172 |
} else { |
||
173 |
101474 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, |
|
174 |
"not ignoring %s/%s (fn_ignore_file not set)", |
||
175 |
path.c_str(), dit->d_name); |
||
176 |
} |
||
177 |
|||
178 |
// Notify user about found directory entry |
||
179 |
platform_stat64 info; |
||
180 |
101926 |
int retval = platform_lstat((path + "/" + dit->d_name).c_str(), &info); |
|
181 |
✗✓✗✓ ✗✓✗✓ ✗✓ |
101926 |
if (retval != 0) { |
182 |
LogCvmfs(kLogFsTraversal, kLogStderr, "failed to lstat '%s' errno: %d", |
||
183 |
(path + "/" + dit->d_name).c_str(), errno); |
||
184 |
abort(); |
||
185 |
} |
||
186 |
✓✓✓✓ ✓✓✓✗ ✓✓ |
101926 |
if (S_ISDIR(info.st_mode)) { |
187 |
95915 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing directory %s/%s", |
|
188 |
path.c_str(), dit->d_name); |
||
189 |
✓✓✓✓ ✓✗✗✗ ✗✗✗✗ ✓✗✓✓ ✓✓✓✗ ✓✗✗✗ ✗✗✓✗ ✓✓✓✗ ✓✗✓✗ ✗✗✗✗ ✓✗✓✗ ✓✗✗✓ ✓✗✗✗ ✗✗✓✗ ✗✓✓✗ ✓✗✓✗ ✗✗✗✗ ✓✗✓✗ |
95915 |
if (Notify(fn_new_dir_prefix, path, dit->d_name) && recurse_) { |
190 |
2254 |
DoRecursion(path, dit->d_name); |
|
191 |
} |
||
192 |
95915 |
Notify(fn_new_dir_postfix, path, dit->d_name); |
|
193 |
✓✓✓✓ ✓✓✗✗ ✓✓ |
6011 |
} else if (S_ISREG(info.st_mode)) { |
194 |
5586 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing regular file %s/%s", |
|
195 |
path.c_str(), dit->d_name); |
||
196 |
5586 |
Notify(fn_new_file, path, dit->d_name); |
|
197 |
✓✓✓✓ ✓✓✗✗ ✓✓ |
425 |
} else if (S_ISLNK(info.st_mode)) { |
198 |
24 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing symlink %s/%s", |
|
199 |
path.c_str(), dit->d_name); |
||
200 |
24 |
Notify(fn_new_symlink, path, dit->d_name); |
|
201 |
✓✓✓✓ ✓✓✗✗ ✓✓ |
401 |
} else if (S_ISSOCK(info.st_mode)) { |
202 |
24 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing socket %s/%s", |
|
203 |
path.c_str(), dit->d_name); |
||
204 |
24 |
Notify(fn_new_socket, path, dit->d_name); |
|
205 |
✓✓✗✓ ✗✓✗✗ ✗✓ |
377 |
} else if (S_ISBLK(info.st_mode)) { |
206 |
54 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing block-device %s/%s", |
|
207 |
path.c_str(), dit->d_name); |
||
208 |
54 |
Notify(fn_new_block_dev, path, dit->d_name); |
|
209 |
✓✓✗✓ ✗✓✗✗ ✗✓ |
323 |
} else if (S_ISCHR(info.st_mode)) { |
210 |
300 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing character-device " |
|
211 |
"%s/%s", |
||
212 |
path.c_str(), dit->d_name); |
||
213 |
300 |
Notify(fn_new_character_dev, path, dit->d_name); |
|
214 |
✓✗✓✗ ✓✗✗✗ ✓✗ |
23 |
} else if (S_ISFIFO(info.st_mode)) { |
215 |
23 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing FIFO %s/%s", |
|
216 |
path.c_str(), dit->d_name); |
||
217 |
23 |
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 |
3172 |
closedir(dip); |
|
226 |
3172 |
LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "leaving %s", path.c_str()); |
|
227 |
3172 |
Notify(fn_leave_dir, parent_path, dir_name); |
|
228 |
3172 |
} |
|
229 |
|||
230 |
96376 |
inline bool Notify(const BoolCallback callback, |
|
231 |
const std::string &parent_path, |
||
232 |
const std::string &entry_name) const |
||
233 |
{ |
||
234 |
return (callback == NULL) ? true : |
||
235 |
(delegate_->*callback)(GetRelativePath(parent_path), |
||
236 |
✓✓✗✓ ✓✓✗✗ ✓✗✓✗ ✓✗✗✗ ✓✗✓✓ ✓✗✗✗ ✓✗✓✗ ✓✗✗✗ ✓✗✓✗ ✓✗✗✗ |
96376 |
entry_name); |
237 |
} |
||
238 |
|||
239 |
108270 |
inline void Notify(const VoidCallback callback, |
|
240 |
const std::string &parent_path, |
||
241 |
const std::string &entry_name) const |
||
242 |
{ |
||
243 |
✓✓✓✗ ✓✗✓✗ ✓✗ |
108270 |
if (callback != NULL) { |
244 |
✗✓✓✗ ✓✗✓✗ ✓✗ |
9140 |
(delegate_->*callback)(GetRelativePath(parent_path), |
245 |
entry_name); |
||
246 |
} |
||
247 |
108270 |
} |
|
248 |
|||
249 |
100051 |
std::string GetRelativePath(const std::string &absolute_path) const { |
|
250 |
100051 |
const unsigned int rel_dir_len = relative_to_directory_.length(); |
|
251 |
✓✓✓✓ ✓✓✓✗ ✓✓ |
100051 |
if (rel_dir_len >= absolute_path.length()) { |
252 |
620 |
return ""; |
|
253 |
✗✓✓✗ ✓✗✗✗ ✓✗ |
99431 |
} else if (rel_dir_len > 1) { |
254 |
542 |
return absolute_path.substr(rel_dir_len + 1); |
|
255 |
✓✗✗✗ ✗✗✗✗ ✗✗ |
98889 |
} else if (rel_dir_len == 0) { |
256 |
98889 |
return absolute_path; |
|
257 |
} else if (relative_to_directory_ == "/") { |
||
258 |
return absolute_path.substr(1); |
||
259 |
} else { |
||
260 |
return ""; |
||
261 |
} |
||
262 |
} |
||
263 |
}; // FileSystemTraversal |
||
264 |
|||
265 |
#ifdef CVMFS_NAMESPACE_GUARD |
||
266 |
} // namespace CVMFS_NAMESPACE_GUARD |
||
267 |
#endif |
||
268 |
|||
269 |
#endif // CVMFS_FS_TRAVERSAL_H_ |
Generated by: GCOVR (Version 4.1) |