GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/pathspec/pathspec.cc Lines: 171 180 95.0 %
Date: 2019-02-03 02:48:13 Branches: 89 107 83.2 %

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 "logging.h"
10
#include "smalloc.h"
11
12
270
Pathspec::Pathspec(const std::string &spec) :
13
  regex_compiled_(false),
14
  regex_(NULL),
15
  relaxed_regex_compiled_(false),
16
  relaxed_regex_(NULL),
17
  prefix_regex_compiled_(false),
18
  prefix_regex_(NULL),
19
  glob_string_compiled_(false),
20
  glob_string_sequence_compiled_(false),
21
  valid_(true),
22
270
  absolute_(false)
23
{
24
270
  Parse(spec);
25
270
  if (patterns_.size() == 0) {
26
1
    valid_ = false;
27
  }
28
29
270
        ElementPatterns::const_iterator i    = patterns_.begin();
30
270
  const ElementPatterns::const_iterator iend = patterns_.end();
31
897
  for (; i != iend; ++i) {
32
627
    if (!i->IsValid()) {
33
5
      valid_ = false;
34
    }
35
  }
36
}
37
38
445
Pathspec::Pathspec(const Pathspec &other) :
39
  patterns_(other.patterns_),
40
  regex_compiled_(false),          // compiled regex structure cannot be
41
  regex_(NULL),                    // duplicated and needs to be re-compiled
42
  relaxed_regex_compiled_(false),  // Note: the copy-constructed object will
43
  relaxed_regex_(NULL),            //       perform a lazy evaluation again
44
  prefix_regex_compiled_(false),
45
  prefix_regex_(NULL),
46
  glob_string_compiled_(other.glob_string_compiled_),
47
  glob_string_(other.glob_string_),
48
  glob_string_sequence_compiled_(other.glob_string_sequence_compiled_),
49
  glob_string_sequence_(other.glob_string_sequence_),
50
  valid_(other.valid_),
51
445
  absolute_(other.absolute_) {}
52
53
715
Pathspec::~Pathspec() {
54
715
  DestroyRegularExpressions();
55
}
56
57
3
Pathspec& Pathspec::operator=(const Pathspec &other) {
58
3
  if (this != &other) {
59
3
    DestroyRegularExpressions();  // see: copy c'tor for details
60
3
    patterns_ = other.patterns_;
61
62
3
    glob_string_compiled_ = other.glob_string_compiled_;
63
3
    glob_string_          = other.glob_string_;
64
65
3
    glob_string_sequence_compiled_ = other.glob_string_sequence_compiled_;
66
3
    glob_string_sequence_          = other.glob_string_sequence_;
67
68
3
    valid_    = other.valid_;
69
3
    absolute_ = other.absolute_;
70
  }
71
72
3
  return *this;
73
}
74
75
76
270
void Pathspec::Parse(const std::string &spec) {
77
  // parsing is done using std::string iterators to walk through the entire
78
  // pathspec parameter. Thus, all parsing methods receive references to these
79
  // iterators and increment itr as they pass along.
80
270
        std::string::const_iterator itr = spec.begin();
81
270
  const std::string::const_iterator end = spec.end();
82
83
270
  absolute_ = (*itr == kSeparator);
84
1745
  while (itr != end) {
85
1205
    if (*itr == kSeparator) {
86
578
      ++itr;
87
578
      continue;
88
    }
89
627
    ParsePathElement(end, &itr);
90
  }
91
270
}
92
93
627
void Pathspec::ParsePathElement(
94
  const std::string::const_iterator &end,
95
  std::string::const_iterator  *itr
96
) {
97
  // find the end of the current pattern element (next directory boundary)
98
627
  const std::string::const_iterator begin_element = *itr;
99

3618
  while (*itr != end && **itr != kSeparator) {
100
2364
    ++(*itr);
101
  }
102
627
  const std::string::const_iterator end_element = *itr;
103
104
  // create a PathspecElementPattern out of this directory description
105
627
  patterns_.push_back(PathspecElementPattern(begin_element, end_element));
106
627
}
107
108
551
bool Pathspec::IsMatching(const std::string &query_path) const {
109
551
  assert(IsValid());
110
111
551
  if (query_path.empty()) {
112
17
    return false;
113
  }
114
115
534
  const bool query_is_absolute = (query_path[0] == kSeparator);
116
  return (!query_is_absolute || this->IsAbsolute()) &&
117

534
         IsPathspecMatching(query_path);
118
}
119
120
12
bool Pathspec::IsPrefixMatching(const std::string &query_path) const {
121
12
  assert(IsValid());
122
12
  assert(IsAbsolute());
123
124
12
  if (query_path.empty()) {
125
    return false;
126
  }
127
128
12
  const bool query_is_absolute = (query_path[0] == kSeparator);
129

12
  return (query_is_absolute && IsPathspecPrefixMatching(query_path));
130
}
131
132
666
bool Pathspec::IsMatchingRelaxed(const std::string &query_path) const {
133
666
  assert(IsValid());
134
135
666
  if (query_path.empty()) {
136
36
    return false;
137
  }
138
139
630
  return IsPathspecMatchingRelaxed(query_path);
140
}
141
142
521
bool Pathspec::IsPathspecMatching(const std::string &query_path) const {
143
521
  return ApplyRegularExpression(query_path, GetRegularExpression());
144
}
145
146
11
bool Pathspec::IsPathspecPrefixMatching(const std::string &query_path) const {
147
11
  return ApplyRegularExpression(query_path, GetPrefixRegularExpression());
148
}
149
150
630
bool Pathspec::IsPathspecMatchingRelaxed(const std::string &query_path) const {
151
630
  return ApplyRegularExpression(query_path, GetRelaxedRegularExpression());
152
}
153
154
1162
bool Pathspec::ApplyRegularExpression(const std::string  &query_path,
155
                                            regex_t      *regex) const {
156
1162
  const char *path = query_path.c_str();
157
1162
  const int retval = regexec(regex, path, 0, NULL, 0);
158
159

1162
  if (retval != 0 && retval != REG_NOMATCH) {
160
    PrintRegularExpressionError(retval);
161
  }
162
163
1162
  return (retval == 0);
164
}
165
166
521
regex_t* Pathspec::GetRegularExpression() const {
167
521
  if (!regex_compiled_) {
168
93
    const bool is_relaxed = false;
169
93
    const std::string regex = GenerateRegularExpression(is_relaxed);
170
93
    LogCvmfs(kLogPathspec, kLogDebug, "compiled regex: %s", regex.c_str());
171
172
93
    regex_ = CompileRegularExpression(regex);
173
93
    regex_compiled_ = true;
174
  }
175
176
521
  return regex_;
177
}
178
179
11
regex_t* Pathspec::GetPrefixRegularExpression() const {
180
11
  if (!prefix_regex_compiled_) {
181
2
    const bool is_relaxed = false;
182
2
    const bool is_prefix = true;
183
2
    const std::string regex = GenerateRegularExpression(is_relaxed, is_prefix);
184
2
    LogCvmfs(kLogPathspec, kLogDebug, "compiled regex: %s", regex.c_str());
185
186
2
    prefix_regex_ = CompileRegularExpression(regex);
187
2
    prefix_regex_compiled_ = true;
188
  }
189
190
11
  return prefix_regex_;
191
}
192
193
630
regex_t* Pathspec::GetRelaxedRegularExpression() const {
194
630
  if (!relaxed_regex_compiled_) {
195
55
    const bool is_relaxed = true;
196
55
    const std::string regex = GenerateRegularExpression(is_relaxed);
197
    LogCvmfs(kLogPathspec, kLogDebug, "compiled relaxed regex: %s",
198
55
             regex.c_str());
199
200
55
    relaxed_regex_ = CompileRegularExpression(regex);
201
55
    relaxed_regex_compiled_ = true;
202
  }
203
204
630
  return relaxed_regex_;
205
}
206
207
150
std::string Pathspec::GenerateRegularExpression(
208
  const bool is_relaxed,
209
  const bool is_prefix) const
210
{
211
  // start matching at the first character
212
150
  std::string regex = "^";
213
214
  // absolute paths require a / in the beginning
215
150
  if (IsAbsolute()) {
216
125
    regex += kSeparator;
217
  }
218
219
  // concatenate the regular expressions of the compiled path elements
220
150
        ElementPatterns::const_iterator i    = patterns_.begin();
221
150
  const ElementPatterns::const_iterator iend = patterns_.end();
222
488
  for (; i != iend; ++i) {
223
338
    regex += i->GenerateRegularExpression(is_relaxed);
224
338
    if (i + 1 != iend) {
225
188
      regex += kSeparator;
226
    }
227
  }
228
229
150
  if (is_prefix) {
230
2
    regex += "($|";
231
2
    regex += kSeparator;
232
2
    regex += ".*$)";
233
  } else {
234
    // a path might end with a trailing slash
235
    // (pathspec does not distinguish files and directories)
236
148
    regex += kSeparator;
237
148
    regex += "?$";
238
  }
239
240
150
  return regex;
241
}
242
243
150
regex_t* Pathspec::CompileRegularExpression(const std::string &regex) const {
244
150
  regex_t *result = reinterpret_cast<regex_t *>(smalloc(sizeof(regex_t)));
245
150
  const int flags = REG_NOSUB | REG_NEWLINE | REG_EXTENDED;
246
150
  const int retval = regcomp(result, regex.c_str(), flags);
247
248
150
  if (retval != 0) {
249
    PrintRegularExpressionError(retval);
250
    assert(false && "failed to compile regex");
251
  }
252
253
150
  return result;
254
}
255
256
718
void Pathspec::DestroyRegularExpressions() {
257
718
  if (regex_compiled_) {
258
93
    assert(regex_ != NULL);
259
93
    regfree(regex_);
260
93
    regex_          = NULL;
261
93
    regex_compiled_ = false;
262
  }
263
264
718
  if (relaxed_regex_compiled_) {
265
55
    assert(relaxed_regex_ != NULL);
266
55
    regfree(relaxed_regex_);
267
55
    relaxed_regex_          = NULL;
268
55
    relaxed_regex_compiled_ = false;
269
  }
270
718
}
271
272
168
bool Pathspec::operator==(const Pathspec &other) const {
273


168
  if (patterns_.size() != other.patterns_.size() ||
274
      IsValid()        != other.IsValid()        ||
275
      IsAbsolute()     != other.IsAbsolute()) {
276
83
    return false;
277
  }
278
279
85
        ElementPatterns::const_iterator i    = patterns_.begin();
280
85
  const ElementPatterns::const_iterator iend = patterns_.end();
281
85
        ElementPatterns::const_iterator j    = other.patterns_.begin();
282
85
  const ElementPatterns::const_iterator jend = other.patterns_.end();
283
284

199
  for (; i != iend && j != jend; ++i, ++j) {
285
180
    if (*i != *j) {
286
66
      return false;
287
    }
288
  }
289
290
19
  return true;
291
}
292
293
void Pathspec::PrintRegularExpressionError(const int error_code) const {
294
  assert(regex_compiled_);
295
  const size_t errbuf_size = 1024;
296
  char error[errbuf_size];
297
  regerror(error_code, regex_, error, errbuf_size);
298
  LogCvmfs(kLogPathspec, kLogStderr, "RegEx Error: %d - %s", error_code, error);
299
}
300
301
17
const Pathspec::GlobStringSequence& Pathspec::GetGlobStringSequence() const {
302
17
  if (!glob_string_sequence_compiled_) {
303
17
    GenerateGlobStringSequence();
304
17
    glob_string_sequence_compiled_ = true;
305
  }
306
17
  return glob_string_sequence_;
307
}
308
309
310
17
void Pathspec::GenerateGlobStringSequence() const {
311
17
  assert(glob_string_sequence_.empty());
312
17
        ElementPatterns::const_iterator i    = patterns_.begin();
313
17
  const ElementPatterns::const_iterator iend = patterns_.end();
314
17
  for (; i != iend; ++i) {
315
34
    const std::string glob_string = i->GenerateGlobString();
316
34
    glob_string_sequence_.push_back(glob_string);
317
  }
318
17
}
319
320
321
23
const std::string& Pathspec::GetGlobString() const {
322
23
  if (!glob_string_compiled_) {
323
17
    GenerateGlobString();
324
17
    glob_string_compiled_ = true;
325
  }
326
23
  return glob_string_;
327
}
328
329
330
17
void Pathspec::GenerateGlobString() const {
331
17
  assert(glob_string_.empty());
332
333
17
  bool is_first = true;
334
17
  const GlobStringSequence &seq = GetGlobStringSequence();
335
17
        GlobStringSequence::const_iterator i    = seq.begin();
336
17
  const GlobStringSequence::const_iterator iend = seq.end();
337
51
  for (; i != iend; ++i) {
338

34
    if (!is_first || IsAbsolute()) {
339
28
      glob_string_ += kSeparator;
340
    }
341
34
    glob_string_ += *i;
342
34
    is_first = false;
343
  }
344
17
}