GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/pathspec/pathspec.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 198 207 95.7%
Branches: 120 172 69.8%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "pathspec.h"
6
7 #include <cassert>
8
9 #include "util/logging.h"
10 #include "util/smalloc.h"
11
12 5221 Pathspec::Pathspec(const std::string &spec)
13 5221 : regex_(NULL)
14 5221 , relaxed_regex_(NULL)
15 5221 , prefix_regex_(NULL)
16 5221 , regex_compiled_(false)
17 5221 , relaxed_regex_compiled_(false)
18 5221 , prefix_regex_compiled_(false)
19 5221 , glob_string_compiled_(false)
20 5221 , glob_string_sequence_compiled_(false)
21 5221 , valid_(true)
22 5221 , absolute_(false) {
23
1/2
✓ Branch 1 taken 5221 times.
✗ Branch 2 not taken.
5221 Parse(spec);
24
2/2
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 5186 times.
5221 if (patterns_.size() == 0) {
25 35 valid_ = false;
26 }
27
28 5221 ElementPatterns::const_iterator i = patterns_.begin();
29 5221 const ElementPatterns::const_iterator iend = patterns_.end();
30
2/2
✓ Branch 2 taken 11189 times.
✓ Branch 3 taken 5221 times.
16410 for (; i != iend; ++i) {
31
2/2
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 11148 times.
11189 if (!i->IsValid()) {
32 41 valid_ = false;
33 }
34 }
35 5221 }
36
37 // Compiled regex structure cannot be duplicated and needs to be re-compiled
38 // Note: the copy-constructed object will perform a lazy evaluation again
39 4963 Pathspec::Pathspec(const Pathspec &other)
40 4963 : patterns_(other.patterns_)
41 4963 , regex_(NULL)
42 4963 , relaxed_regex_(NULL)
43 4963 , prefix_regex_(NULL)
44
1/2
✓ Branch 1 taken 4963 times.
✗ Branch 2 not taken.
4963 , glob_string_(other.glob_string_)
45
1/2
✓ Branch 1 taken 4963 times.
✗ Branch 2 not taken.
4963 , glob_string_sequence_(other.glob_string_sequence_)
46 4963 , regex_compiled_(false)
47 4963 , relaxed_regex_compiled_(false)
48 4963 , prefix_regex_compiled_(false)
49 4963 , glob_string_compiled_(other.glob_string_compiled_)
50 4963 , glob_string_sequence_compiled_(other.glob_string_sequence_compiled_)
51 4963 , valid_(other.valid_)
52 4963 , absolute_(other.absolute_) { }
53
54 10181 Pathspec::~Pathspec() { DestroyRegularExpressions(); }
55
56 108 Pathspec &Pathspec::operator=(const Pathspec &other) {
57
1/2
✓ Branch 0 taken 108 times.
✗ Branch 1 not taken.
108 if (this != &other) {
58 108 DestroyRegularExpressions(); // see: copy c'tor for details
59 108 patterns_ = other.patterns_;
60
61 108 glob_string_compiled_ = other.glob_string_compiled_;
62 108 glob_string_ = other.glob_string_;
63
64 108 glob_string_sequence_compiled_ = other.glob_string_sequence_compiled_;
65 108 glob_string_sequence_ = other.glob_string_sequence_;
66
67 108 valid_ = other.valid_;
68 108 absolute_ = other.absolute_;
69 }
70
71 108 return *this;
72 }
73
74
75 5221 void Pathspec::Parse(const std::string &spec) {
76 // parsing is done using std::string iterators to walk through the entire
77 // pathspec parameter. Thus, all parsing methods receive references to these
78 // iterators and increment itr as they pass along.
79 5221 std::string::const_iterator itr = spec.begin();
80 5221 const std::string::const_iterator end = spec.end();
81
82 5221 absolute_ = (*itr == kSeparator);
83
2/2
✓ Branch 1 taken 21235 times.
✓ Branch 2 taken 5221 times.
26456 while (itr != end) {
84
2/2
✓ Branch 1 taken 10046 times.
✓ Branch 2 taken 11189 times.
21235 if (*itr == kSeparator) {
85 10046 ++itr;
86 10046 continue;
87 }
88
1/2
✓ Branch 1 taken 11189 times.
✗ Branch 2 not taken.
11189 ParsePathElement(end, &itr);
89 }
90 5221 }
91
92 11189 void Pathspec::ParsePathElement(const std::string::const_iterator &end,
93 std::string::const_iterator *itr) {
94 // find the end of the current pattern element (next directory boundary)
95 11189 const std::string::const_iterator begin_element = *itr;
96
6/6
✓ Branch 1 taken 56816 times.
✓ Branch 2 taken 4870 times.
✓ Branch 4 taken 50497 times.
✓ Branch 5 taken 6319 times.
✓ Branch 6 taken 50497 times.
✓ Branch 7 taken 11189 times.
61686 while (*itr != end && **itr != kSeparator) {
97 50497 ++(*itr);
98 }
99 11189 const std::string::const_iterator end_element = *itr;
100
101 // create a PathspecElementPattern out of this directory description
102
2/4
✓ Branch 1 taken 11189 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11189 times.
✗ Branch 5 not taken.
11189 patterns_.push_back(PathspecElementPattern(begin_element, end_element));
103 11189 }
104
105 14486 bool Pathspec::IsMatching(const std::string &query_path) const {
106
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 14486 times.
14486 assert(IsValid());
107
108
2/2
✓ Branch 1 taken 756 times.
✓ Branch 2 taken 13730 times.
14486 if (query_path.empty()) {
109 756 return false;
110 }
111
112 13730 const bool query_is_absolute = (query_path[0] == kSeparator);
113
2/2
✓ Branch 1 taken 10264 times.
✓ Branch 2 taken 456 times.
10720 return (!query_is_absolute || this->IsAbsolute())
114
4/4
✓ Branch 0 taken 10720 times.
✓ Branch 1 taken 3010 times.
✓ Branch 3 taken 4818 times.
✓ Branch 4 taken 8456 times.
24450 && IsPathspecMatching(query_path);
115 }
116
117 432 bool Pathspec::IsPrefixMatching(const std::string &query_path) const {
118
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 432 times.
432 assert(IsValid());
119
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 432 times.
432 assert(IsAbsolute());
120
121
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 432 times.
432 if (query_path.empty()) {
122 ✗ return false;
123 }
124
125 432 const bool query_is_absolute = (query_path[0] == kSeparator);
126
4/4
✓ Branch 0 taken 396 times.
✓ Branch 1 taken 36 times.
✓ Branch 3 taken 252 times.
✓ Branch 4 taken 144 times.
432 return (query_is_absolute && IsPathspecPrefixMatching(query_path));
127 }
128
129 9293 bool Pathspec::IsMatchingRelaxed(const std::string &query_path) const {
130
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9293 times.
9293 assert(IsValid());
131
132
2/2
✓ Branch 1 taken 1662 times.
✓ Branch 2 taken 7631 times.
9293 if (query_path.empty()) {
133 1662 return false;
134 }
135
136 7631 return IsPathspecMatchingRelaxed(query_path);
137 }
138
139 13274 bool Pathspec::IsPathspecMatching(const std::string &query_path) const {
140 13274 return ApplyRegularExpression(query_path, GetRegularExpression());
141 }
142
143 396 bool Pathspec::IsPathspecPrefixMatching(const std::string &query_path) const {
144 396 return ApplyRegularExpression(query_path, GetPrefixRegularExpression());
145 }
146
147 7631 bool Pathspec::IsPathspecMatchingRelaxed(const std::string &query_path) const {
148 7631 return ApplyRegularExpression(query_path, GetRelaxedRegularExpression());
149 }
150
151 21301 bool Pathspec::ApplyRegularExpression(const std::string &query_path,
152 regex_t *regex) const {
153 21301 const char *path = query_path.c_str();
154 21301 const int retval = regexec(regex, path, 0, NULL, 0);
155
156
3/4
✓ Branch 0 taken 14976 times.
✓ Branch 1 taken 6325 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 14976 times.
21301 if (retval != 0 && retval != REG_NOMATCH) {
157 ✗ PrintRegularExpressionError(retval);
158 }
159
160 21301 return (retval == 0);
161 }
162
163 13274 regex_t *Pathspec::GetRegularExpression() const {
164
2/2
✓ Branch 0 taken 2756 times.
✓ Branch 1 taken 10518 times.
13274 if (!regex_compiled_) {
165 2756 const bool is_relaxed = false;
166
1/2
✓ Branch 1 taken 2756 times.
✗ Branch 2 not taken.
2756 const std::string regex = GenerateRegularExpression(is_relaxed);
167
1/2
✓ Branch 2 taken 2756 times.
✗ Branch 3 not taken.
2756 LogCvmfs(kLogPathspec, kLogDebug, "compiled regex: %s", regex.c_str());
168
169
1/2
✓ Branch 1 taken 2756 times.
✗ Branch 2 not taken.
2756 regex_ = CompileRegularExpression(regex);
170 2756 regex_compiled_ = true;
171 2756 }
172
173 13274 return regex_;
174 }
175
176 396 regex_t *Pathspec::GetPrefixRegularExpression() const {
177
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 324 times.
396 if (!prefix_regex_compiled_) {
178 72 const bool is_relaxed = false;
179 72 const bool is_prefix = true;
180
1/2
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
72 const std::string regex = GenerateRegularExpression(is_relaxed, is_prefix);
181
1/2
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
72 LogCvmfs(kLogPathspec, kLogDebug, "compiled regex: %s", regex.c_str());
182
183
1/2
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
72 prefix_regex_ = CompileRegularExpression(regex);
184 72 prefix_regex_compiled_ = true;
185 72 }
186
187 396 return prefix_regex_;
188 }
189
190 7631 regex_t *Pathspec::GetRelaxedRegularExpression() const {
191
2/2
✓ Branch 0 taken 825 times.
✓ Branch 1 taken 6806 times.
7631 if (!relaxed_regex_compiled_) {
192 825 const bool is_relaxed = true;
193
1/2
✓ Branch 1 taken 825 times.
✗ Branch 2 not taken.
825 const std::string regex = GenerateRegularExpression(is_relaxed);
194
1/2
✓ Branch 2 taken 825 times.
✗ Branch 3 not taken.
825 LogCvmfs(kLogPathspec, kLogDebug, "compiled relaxed regex: %s",
195 regex.c_str());
196
197
1/2
✓ Branch 1 taken 825 times.
✗ Branch 2 not taken.
825 relaxed_regex_ = CompileRegularExpression(regex);
198 825 relaxed_regex_compiled_ = true;
199 825 }
200
201 7631 return relaxed_regex_;
202 }
203
204 3653 std::string Pathspec::GenerateRegularExpression(const bool is_relaxed,
205 const bool is_prefix) const {
206 // start matching at the first character
207
1/2
✓ Branch 2 taken 3653 times.
✗ Branch 3 not taken.
3653 std::string regex = "^";
208
209 // absolute paths require a / in the beginning
210
2/2
✓ Branch 1 taken 3022 times.
✓ Branch 2 taken 631 times.
3653 if (IsAbsolute()) {
211
1/2
✓ Branch 1 taken 3022 times.
✗ Branch 2 not taken.
3022 regex += kSeparator;
212 }
213
214 // concatenate the regular expressions of the compiled path elements
215 3653 ElementPatterns::const_iterator i = patterns_.begin();
216 3653 const ElementPatterns::const_iterator iend = patterns_.end();
217
2/2
✓ Branch 2 taken 7791 times.
✓ Branch 3 taken 3653 times.
11444 for (; i != iend; ++i) {
218
2/4
✓ Branch 2 taken 7791 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 7791 times.
✗ Branch 6 not taken.
7791 regex += i->GenerateRegularExpression(is_relaxed);
219
2/2
✓ Branch 2 taken 4138 times.
✓ Branch 3 taken 3653 times.
7791 if (i + 1 != iend) {
220
1/2
✓ Branch 1 taken 4138 times.
✗ Branch 2 not taken.
4138 regex += kSeparator;
221 }
222 }
223
224
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 3581 times.
3653 if (is_prefix) {
225
1/2
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
72 regex += "($|";
226
1/2
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
72 regex += kSeparator;
227
1/2
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
72 regex += ".*$)";
228 } else {
229 // a path might end with a trailing slash
230 // (pathspec does not distinguish files and directories)
231
1/2
✓ Branch 1 taken 3581 times.
✗ Branch 2 not taken.
3581 regex += kSeparator;
232
1/2
✓ Branch 1 taken 3581 times.
✗ Branch 2 not taken.
3581 regex += "?$";
233 }
234
235 7306 return regex;
236 }
237
238 3653 regex_t *Pathspec::CompileRegularExpression(const std::string &regex) const {
239 3653 regex_t *result = reinterpret_cast<regex_t *>(smalloc(sizeof(regex_t)));
240 3653 const int flags = REG_NOSUB | REG_NEWLINE | REG_EXTENDED;
241 3653 const int retval = regcomp(result, regex.c_str(), flags);
242
243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3653 times.
3653 if (retval != 0) {
244 ✗ PrintRegularExpressionError(retval);
245 ✗ assert(false && "failed to compile regex");
246 }
247
248 3653 return result;
249 }
250
251 10289 void Pathspec::DestroyRegularExpressions() {
252
2/2
✓ Branch 0 taken 2756 times.
✓ Branch 1 taken 7533 times.
10289 if (regex_compiled_) {
253
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2756 times.
2756 assert(regex_ != NULL);
254 2756 regfree(regex_);
255 2756 regex_ = NULL;
256 2756 regex_compiled_ = false;
257 }
258
259
2/2
✓ Branch 0 taken 825 times.
✓ Branch 1 taken 9464 times.
10289 if (relaxed_regex_compiled_) {
260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 825 times.
825 assert(relaxed_regex_ != NULL);
261 825 regfree(relaxed_regex_);
262 825 relaxed_regex_ = NULL;
263 825 relaxed_regex_compiled_ = false;
264 }
265 10289 }
266
267 2758 bool Pathspec::operator==(const Pathspec &other) const {
268
1/2
✓ Branch 4 taken 1554 times.
✗ Branch 5 not taken.
4312 if (patterns_.size() != other.patterns_.size() || IsValid() != other.IsValid()
269
6/6
✓ Branch 0 taken 1554 times.
✓ Branch 1 taken 1204 times.
✓ Branch 4 taken 100 times.
✓ Branch 5 taken 1454 times.
✓ Branch 6 taken 1304 times.
✓ Branch 7 taken 1454 times.
4312 || IsAbsolute() != other.IsAbsolute()) {
270 1304 return false;
271 }
272
273 1454 ElementPatterns::const_iterator i = patterns_.begin();
274 1454 const ElementPatterns::const_iterator iend = patterns_.end();
275 1454 ElementPatterns::const_iterator j = other.patterns_.begin();
276 1454 const ElementPatterns::const_iterator jend = other.patterns_.end();
277
278
5/6
✓ Branch 3 taken 2972 times.
✓ Branch 4 taken 546 times.
✓ Branch 6 taken 2972 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2972 times.
✓ Branch 9 taken 546 times.
3518 for (; i != iend && j != jend; ++i, ++j) {
279
3/4
✓ Branch 3 taken 2972 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 908 times.
✓ Branch 6 taken 2064 times.
2972 if (*i != *j) {
280 908 return false;
281 }
282 }
283
284 546 return true;
285 }
286
287 ✗ void Pathspec::PrintRegularExpressionError(const int error_code) const {
288 ✗ assert(regex_compiled_);
289 ✗ const size_t errbuf_size = 1024;
290 char error[errbuf_size];
291 ✗ regerror(error_code, regex_, error, errbuf_size);
292 ✗ LogCvmfs(kLogPathspec, kLogStderr, "RegEx Error: %d - %s", error_code, error);
293 }
294
295 599 const Pathspec::GlobStringSequence &Pathspec::GetGlobStringSequence() const {
296
1/2
✓ Branch 0 taken 599 times.
✗ Branch 1 not taken.
599 if (!glob_string_sequence_compiled_) {
297 599 GenerateGlobStringSequence();
298 599 glob_string_sequence_compiled_ = true;
299 }
300 599 return glob_string_sequence_;
301 }
302
303
304 599 void Pathspec::GenerateGlobStringSequence() const {
305
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 599 times.
599 assert(glob_string_sequence_.empty());
306 599 ElementPatterns::const_iterator i = patterns_.begin();
307 599 const ElementPatterns::const_iterator iend = patterns_.end();
308
2/2
✓ Branch 2 taken 1198 times.
✓ Branch 3 taken 599 times.
1797 for (; i != iend; ++i) {
309
1/2
✓ Branch 2 taken 1198 times.
✗ Branch 3 not taken.
1198 const std::string glob_string = i->GenerateGlobString();
310
1/2
✓ Branch 1 taken 1198 times.
✗ Branch 2 not taken.
1198 glob_string_sequence_.push_back(glob_string);
311 1198 }
312 599 }
313
314
315 812 const std::string &Pathspec::GetGlobString() const {
316
2/2
✓ Branch 0 taken 599 times.
✓ Branch 1 taken 213 times.
812 if (!glob_string_compiled_) {
317 599 GenerateGlobString();
318 599 glob_string_compiled_ = true;
319 }
320 812 return glob_string_;
321 }
322
323
324 599 void Pathspec::GenerateGlobString() const {
325
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 599 times.
599 assert(glob_string_.empty());
326
327 599 bool is_first = true;
328
1/2
✓ Branch 1 taken 599 times.
✗ Branch 2 not taken.
599 const GlobStringSequence &seq = GetGlobStringSequence();
329 599 GlobStringSequence::const_iterator i = seq.begin();
330 599 const GlobStringSequence::const_iterator iend = seq.end();
331
2/2
✓ Branch 1 taken 1198 times.
✓ Branch 2 taken 599 times.
1797 for (; i != iend; ++i) {
332
6/6
✓ Branch 0 taken 599 times.
✓ Branch 1 taken 599 times.
✓ Branch 3 taken 389 times.
✓ Branch 4 taken 210 times.
✓ Branch 5 taken 988 times.
✓ Branch 6 taken 210 times.
1198 if (!is_first || IsAbsolute()) {
333
1/2
✓ Branch 1 taken 988 times.
✗ Branch 2 not taken.
988 glob_string_ += kSeparator;
334 }
335
1/2
✓ Branch 2 taken 1198 times.
✗ Branch 3 not taken.
1198 glob_string_ += *i;
336 1198 is_first = false;
337 }
338 599 }
339