GCC Code Coverage Report


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