GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/path_filters/inclusion_spec.cc
Date: 2026-08-02 02:40:19
Exec Total Coverage
Lines: 75 81 92.6%
Branches: 89 140 63.6%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "path_filters/inclusion_spec.h"
6
7 #include <cstdio>
8 #include <cstdlib>
9 #include <string>
10
11 #include "util/logging.h"
12 #include "util/posix.h"
13 #include "util/string.h"
14
15 using namespace catalog; // NOLINT
16
17
18
1/2
✓ Branch 2 taken 234 times.
✗ Branch 3 not taken.
234 InclusionSpec::InclusionSpec() : valid_(false), version_(-1) {}
19
20 234 InclusionSpec::~InclusionSpec() {}
21
22
23 26 InclusionSpec *InclusionSpec::Create(const std::string &spec_path) {
24
2/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
26 InclusionSpec *spec = new InclusionSpec();
25
26
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 FILE *f = fopen(spec_path.c_str(), "r");
27
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 13 times.
26 if (f == NULL) {
28
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 LogCvmfs(kLogCvmfs, kLogStderr,
29 "InclusionSpec: cannot open spec file '%s'",
30 spec_path.c_str());
31
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 delete spec;
32 13 return NULL;
33 }
34
35 13 std::string content;
36 char buf[4096];
37 size_t nbytes;
38
3/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 13 times.
26 while ((nbytes = fread(buf, 1, sizeof(buf), f)) > 0) {
39
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 content.append(buf, nbytes);
40 }
41
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 fclose(f);
42
43
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
13 if (!spec->Parse(content)) {
44 delete spec;
45 return NULL;
46 }
47 13 return spec;
48 13 }
49
50
51 169 bool InclusionSpec::Parse(const std::string &spec) {
52 169 valid_ = false;
53
1/2
✓ Branch 1 taken 169 times.
✗ Branch 2 not taken.
169 content_ = spec;
54
55 // Split into lines and find the version line
56
1/2
✓ Branch 1 taken 169 times.
✗ Branch 2 not taken.
169 std::vector<std::string> lines = SplitString(spec, '\n');
57
58 // Find the first non-comment, non-blank line — must be "version N"
59 169 bool found_version = false;
60
2/2
✓ Branch 1 taken 221 times.
✓ Branch 2 taken 26 times.
247 for (size_t i = 0; i < lines.size(); ++i) {
61
1/2
✓ Branch 2 taken 221 times.
✗ Branch 3 not taken.
221 std::string line = Trim(lines[i]);
62
7/8
✓ Branch 1 taken 182 times.
✓ Branch 2 taken 39 times.
✓ Branch 4 taken 182 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 39 times.
✓ Branch 7 taken 143 times.
✓ Branch 8 taken 78 times.
✓ Branch 9 taken 143 times.
221 if (line.empty() || line[0] == '#') {
63 78 continue;
64 }
65
3/4
✓ Branch 1 taken 143 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 130 times.
143 if (!ParseVersion(line)) {
66
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 LogCvmfs(kLogCvmfs, kLogStderr,
67 "InclusionSpec: first non-comment line must be 'version N', "
68 "got '%s'",
69 line.c_str());
70 13 return false;
71 }
72 130 found_version = true;
73 130 break;
74
3/3
✓ Branch 1 taken 78 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 130 times.
221 }
75
76
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 130 times.
156 if (!found_version) {
77
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 LogCvmfs(kLogCvmfs, kLogStderr,
78 "InclusionSpec: spec file is empty or contains only comments");
79 26 return false;
80 }
81
82
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 117 times.
130 if (version_ != kCurrentVersion) {
83
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 LogCvmfs(kLogCvmfs, kLogStderr,
84 "InclusionSpec: unsupported version %d (expected %d)",
85 version_, kCurrentVersion);
86 13 return false;
87 }
88
89 // Parse the remaining lines as path rules using RelaxedPathFilter
90
1/2
✓ Branch 1 taken 117 times.
✗ Branch 2 not taken.
117 const std::string filter_content = StripVersionLine(spec);
91
2/4
✓ Branch 1 taken 117 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 117 times.
117 if (!filter_.Parse(filter_content)) {
92 LogCvmfs(kLogCvmfs, kLogStderr,
93 "InclusionSpec: failed to parse path rules");
94 return false;
95 }
96
97 117 valid_ = true;
98 117 return true;
99 169 }
100
101
102 338 bool InclusionSpec::IsExcluded(const std::string &path) const {
103
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 325 times.
338 if (!valid_)
104 13 return false;
105
106 // Root catalog is never excluded
107
6/6
✓ Branch 1 taken 312 times.
✓ Branch 2 taken 13 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 299 times.
✓ Branch 6 taken 26 times.
✓ Branch 7 taken 299 times.
325 if (path.empty() || path == "/")
108 26 return false;
109
110 // The spec is an inclusion list: positive rules mean "include" (replicate)
111 // and ! rules mean "exclude". RelaxedPathFilter::IsMatching returns true if a
112 // path matches a positive rule (including the parents and sub paths of listed
113 // paths) and is not opposed by a negative ! rule.
114 // So IsMatching == true means "this path is in the inclusion set", which we
115 // replicate; everything else is excluded from object replication.
116 299 return !filter_.IsMatching(path);
117 }
118
119
120 143 bool InclusionSpec::ParseVersion(const std::string &line) {
121 // Expected format: "version N"
122
1/2
✓ Branch 1 taken 143 times.
✗ Branch 2 not taken.
143 const std::string trimmed = Trim(line);
123
3/6
✓ Branch 1 taken 143 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 130 times.
143 if (trimmed.substr(0, 8) != "version ") {
124 13 return false;
125 }
126
2/4
✓ Branch 1 taken 130 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 130 times.
✗ Branch 5 not taken.
130 std::string version_str = Trim(trimmed.substr(8));
127
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 130 times.
130 if (version_str.empty()) {
128 return false;
129 }
130 // Check all digits
131
2/2
✓ Branch 1 taken 143 times.
✓ Branch 2 taken 130 times.
273 for (size_t i = 0; i < version_str.size(); ++i) {
132
5/11
✓ Branch 1 taken 143 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 143 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 143 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 143 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 143 times.
143 if (version_str[i] < '0' || version_str[i] > '9') {
133 return false;
134 }
135 }
136
1/2
✓ Branch 1 taken 130 times.
✗ Branch 2 not taken.
130 version_ = static_cast<int>(String2Uint64(version_str));
137 130 return true;
138 143 }
139
140
141 117 std::string InclusionSpec::StripVersionLine(const std::string &spec) const {
142 // Remove everything up to and including the version line
143
1/2
✓ Branch 1 taken 117 times.
✗ Branch 2 not taken.
117 std::vector<std::string> lines = SplitString(spec, '\n');
144 117 std::string result;
145 117 bool past_version = false;
146
147
2/2
✓ Branch 1 taken 390 times.
✓ Branch 2 taken 117 times.
507 for (size_t i = 0; i < lines.size(); ++i) {
148
2/2
✓ Branch 0 taken 143 times.
✓ Branch 1 taken 247 times.
390 if (!past_version) {
149
1/2
✓ Branch 2 taken 143 times.
✗ Branch 3 not taken.
143 std::string trimmed = Trim(lines[i]);
150
3/4
✓ Branch 1 taken 130 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 117 times.
✓ Branch 4 taken 13 times.
130 if (!trimmed.empty() && trimmed[0] != '#'
151
8/14
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 13 times.
✓ Branch 4 taken 117 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 117 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 117 times.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 117 times.
✓ Branch 13 taken 26 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
273 && trimmed.substr(0, 7) == "version") {
152 117 past_version = true;
153 117 continue;
154 }
155
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 117 times.
143 }
156
2/2
✓ Branch 0 taken 247 times.
✓ Branch 1 taken 26 times.
273 if (past_version) {
157
2/4
✓ Branch 2 taken 247 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 247 times.
✗ Branch 6 not taken.
247 result += lines[i] + "\n";
158 }
159 }
160 234 return result;
161 117 }
162