GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/path_filters/inclusion_spec.cc
Date: 2026-07-12 02:35:48
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 882 times.
✗ Branch 3 not taken.
882 InclusionSpec::InclusionSpec() : valid_(false), version_(-1) {}
19
20 882 InclusionSpec::~InclusionSpec() {}
21
22
23 98 InclusionSpec *InclusionSpec::Create(const std::string &spec_path) {
24
2/4
✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 98 times.
✗ Branch 5 not taken.
98 InclusionSpec *spec = new InclusionSpec();
25
26
1/2
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
98 FILE *f = fopen(spec_path.c_str(), "r");
27
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 49 times.
98 if (f == NULL) {
28
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 LogCvmfs(kLogCvmfs, kLogStderr,
29 "InclusionSpec: cannot open spec file '%s'",
30 spec_path.c_str());
31
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 delete spec;
32 49 return NULL;
33 }
34
35 49 std::string content;
36 char buf[4096];
37 size_t nbytes;
38
3/4
✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 49 times.
98 while ((nbytes = fread(buf, 1, sizeof(buf), f)) > 0) {
39
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 content.append(buf, nbytes);
40 }
41
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 fclose(f);
42
43
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
49 if (!spec->Parse(content)) {
44 delete spec;
45 return NULL;
46 }
47 49 return spec;
48 49 }
49
50
51 637 bool InclusionSpec::Parse(const std::string &spec) {
52 637 valid_ = false;
53
1/2
✓ Branch 1 taken 637 times.
✗ Branch 2 not taken.
637 content_ = spec;
54
55 // Split into lines and find the version line
56
1/2
✓ Branch 1 taken 637 times.
✗ Branch 2 not taken.
637 std::vector<std::string> lines = SplitString(spec, '\n');
57
58 // Find the first non-comment, non-blank line — must be "version N"
59 637 bool found_version = false;
60
2/2
✓ Branch 1 taken 833 times.
✓ Branch 2 taken 98 times.
931 for (size_t i = 0; i < lines.size(); ++i) {
61
1/2
✓ Branch 2 taken 833 times.
✗ Branch 3 not taken.
833 std::string line = Trim(lines[i]);
62
7/8
✓ Branch 1 taken 686 times.
✓ Branch 2 taken 147 times.
✓ Branch 4 taken 686 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 147 times.
✓ Branch 7 taken 539 times.
✓ Branch 8 taken 294 times.
✓ Branch 9 taken 539 times.
833 if (line.empty() || line[0] == '#') {
63 294 continue;
64 }
65
3/4
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 490 times.
539 if (!ParseVersion(line)) {
66
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 LogCvmfs(kLogCvmfs, kLogStderr,
67 "InclusionSpec: first non-comment line must be 'version N', "
68 "got '%s'",
69 line.c_str());
70 49 return false;
71 }
72 490 found_version = true;
73 490 break;
74
3/3
✓ Branch 1 taken 294 times.
✓ Branch 2 taken 49 times.
✓ Branch 3 taken 490 times.
833 }
75
76
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 490 times.
588 if (!found_version) {
77
1/2
✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
98 LogCvmfs(kLogCvmfs, kLogStderr,
78 "InclusionSpec: spec file is empty or contains only comments");
79 98 return false;
80 }
81
82
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 441 times.
490 if (version_ != kCurrentVersion) {
83
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 LogCvmfs(kLogCvmfs, kLogStderr,
84 "InclusionSpec: unsupported version %d (expected %d)",
85 version_, kCurrentVersion);
86 49 return false;
87 }
88
89 // Parse the remaining lines as path rules using RelaxedPathFilter
90
1/2
✓ Branch 1 taken 441 times.
✗ Branch 2 not taken.
441 const std::string filter_content = StripVersionLine(spec);
91
2/4
✓ Branch 1 taken 441 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 441 times.
441 if (!filter_.Parse(filter_content)) {
92 LogCvmfs(kLogCvmfs, kLogStderr,
93 "InclusionSpec: failed to parse path rules");
94 return false;
95 }
96
97 441 valid_ = true;
98 441 return true;
99 637 }
100
101
102 1274 bool InclusionSpec::IsExcluded(const std::string &path) const {
103
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 1225 times.
1274 if (!valid_)
104 49 return false;
105
106 // Root catalog is never excluded
107
6/6
✓ Branch 1 taken 1176 times.
✓ Branch 2 taken 49 times.
✓ Branch 4 taken 49 times.
✓ Branch 5 taken 1127 times.
✓ Branch 6 taken 98 times.
✓ Branch 7 taken 1127 times.
1225 if (path.empty() || path == "/")
108 98 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 1127 return !filter_.IsMatching(path);
117 }
118
119
120 539 bool InclusionSpec::ParseVersion(const std::string &line) {
121 // Expected format: "version N"
122
1/2
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
539 const std::string trimmed = Trim(line);
123
3/6
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 49 times.
✓ Branch 6 taken 490 times.
539 if (trimmed.substr(0, 8) != "version ") {
124 49 return false;
125 }
126
2/4
✓ Branch 1 taken 490 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 490 times.
✗ Branch 5 not taken.
490 std::string version_str = Trim(trimmed.substr(8));
127
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 490 times.
490 if (version_str.empty()) {
128 return false;
129 }
130 // Check all digits
131
2/2
✓ Branch 1 taken 539 times.
✓ Branch 2 taken 490 times.
1029 for (size_t i = 0; i < version_str.size(); ++i) {
132
5/11
✓ Branch 1 taken 539 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 539 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 539 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 539 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 539 times.
539 if (version_str[i] < '0' || version_str[i] > '9') {
133 return false;
134 }
135 }
136
1/2
✓ Branch 1 taken 490 times.
✗ Branch 2 not taken.
490 version_ = static_cast<int>(String2Uint64(version_str));
137 490 return true;
138 539 }
139
140
141 441 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 441 times.
✗ Branch 2 not taken.
441 std::vector<std::string> lines = SplitString(spec, '\n');
144 441 std::string result;
145 441 bool past_version = false;
146
147
2/2
✓ Branch 1 taken 1470 times.
✓ Branch 2 taken 441 times.
1911 for (size_t i = 0; i < lines.size(); ++i) {
148
2/2
✓ Branch 0 taken 539 times.
✓ Branch 1 taken 931 times.
1470 if (!past_version) {
149
1/2
✓ Branch 2 taken 539 times.
✗ Branch 3 not taken.
539 std::string trimmed = Trim(lines[i]);
150
3/4
✓ Branch 1 taken 490 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 441 times.
✓ Branch 4 taken 49 times.
490 if (!trimmed.empty() && trimmed[0] != '#'
151
8/14
✓ Branch 1 taken 490 times.
✓ Branch 2 taken 49 times.
✓ Branch 4 taken 441 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 441 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 441 times.
✓ Branch 10 taken 98 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 441 times.
✓ Branch 13 taken 98 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
1029 && trimmed.substr(0, 7) == "version") {
152 441 past_version = true;
153 441 continue;
154 }
155
2/2
✓ Branch 1 taken 98 times.
✓ Branch 2 taken 441 times.
539 }
156
2/2
✓ Branch 0 taken 931 times.
✓ Branch 1 taken 98 times.
1029 if (past_version) {
157
2/4
✓ Branch 2 taken 931 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 931 times.
✗ Branch 6 not taken.
931 result += lines[i] + "\n";
158 }
159 }
160 882 return result;
161 441 }
162