GCC Code Coverage Report


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