GCC Code Coverage Report


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