GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/repository_tag.cc
Date: 2026-07-19 02:35:15
Exec Total Coverage
Lines: 53 54 98.1%
Branches: 55 74 74.3%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "repository_tag.h"
6
7 #include <cctype>
8 #include <string>
9
10 #include "util/platform.h"
11 #include "util/string.h"
12
13 145 RepositoryTag::RepositoryTag(const std::string &name,
14 145 const std::string &description)
15 145 : name_(name)
16
1/2
✓ Branch 1 taken 145 times.
✗ Branch 2 not taken.
145 , description_(description)
17
1/2
✓ Branch 2 taken 145 times.
✗ Branch 3 not taken.
145 , delete_tags_("")
18 145 , auto_tag_threshold_(0) { }
19
20 /**
21 * Check if tag name is of the form "generic-*"
22 */
23 40 bool RepositoryTag::HasGenericName() {
24
2/4
✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 40 times.
✗ Branch 6 not taken.
40 return HasPrefix(name_, "generic-", false);
25 }
26
27 namespace {
28
29 // True if `s` is exactly "YYYY-MM-DDTHH:MM:SS(.fff)?Z" (1-3 fractional digits),
30 // the timestamp shape produced by RepositoryTag::SetGenericName() and by the
31 // `date -u +%Y-%m-%dT%H:%M:%SZ` call in cvmfs_server_publish.sh. Only the digit
32 // and separator structure is checked, matching the regular expression in
33 // filter_auto_tags() (field ranges are not validated there either).
34 128 bool IsIso8601UtcTimestamp(const std::string &s) {
35 // "YYYY-MM-DDTHH:MM:SSZ" without fractional seconds is 20 characters.
36
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 96 times.
128 if (s.size() < 20) {
37 32 return false;
38 }
39
7/10
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 88 times.
✓ Branch 8 taken 8 times.
✓ Branch 10 taken 88 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 8 times.
✓ Branch 13 taken 88 times.
184 if (s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' ||
40
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 88 times.
88 s[16] != ':') {
41 8 return false;
42 }
43 88 const size_t digit_pos[] = {0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18};
44
2/2
✓ Branch 0 taken 1152 times.
✓ Branch 1 taken 80 times.
1232 for (size_t k = 0; k < sizeof(digit_pos) / sizeof(digit_pos[0]); ++k) {
45
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1144 times.
1152 if (!isdigit(static_cast<unsigned char>(s[digit_pos[k]]))) {
46 8 return false;
47 }
48 }
49 // Optional ".fff" (1-3 digits) follows the seconds.
50 80 size_t pos = 19;
51
2/2
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 32 times.
80 if (s[pos] == '.') {
52 48 ++pos;
53 48 size_t fractional = 0;
54
6/6
✓ Branch 1 taken 160 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 136 times.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 120 times.
✓ Branch 6 taken 48 times.
304 while (pos < s.size() && fractional < 3 &&
55
2/2
✓ Branch 1 taken 120 times.
✓ Branch 2 taken 16 times.
136 isdigit(static_cast<unsigned char>(s[pos]))) {
56 120 ++pos;
57 120 ++fractional;
58 }
59
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (fractional == 0) {
60 return false; // a '.' must be followed by at least one digit
61 }
62 }
63 // The timestamp must end exactly with the 'Z' UTC designator.
64
3/4
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 24 times.
✓ Branch 4 taken 56 times.
✗ Branch 5 not taken.
80 return pos == s.size() - 1 && s[pos] == 'Z';
65 }
66
67 } // anonymous namespace
68
69 /**
70 * True if `name` is a full auto-generated tag name: "generic-<ISO8601>" or,
71 * on name clashes, "generic_<n>-<ISO8601>", e.g.
72 * "generic-2024-01-01T00:00:00.000Z" (see SetGenericName() and the generic tag
73 * handling in cvmfs_server_publish.sh). The "<ISO8601>" part must be a complete
74 * "YYYY-MM-DDTHH:MM:SS(.mmm)?Z" timestamp, so user-created tags such as
75 * "generic-release" or "generic-2024-Z" are not matched and never deleted by
76 * the auto-tag cleanup. This mirrors the regular expression used by the
77 * non-gateway cleanup in filter_auto_tags().
78 */
79 176 bool RepositoryTag::IsAutoGeneratedName(const std::string &name) {
80 176 std::string rest;
81
4/6
✓ Branch 2 taken 176 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 176 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 112 times.
✓ Branch 10 taken 64 times.
176 if (HasPrefix(name, "generic-", false)) {
82
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
112 rest = name.substr(8); // strlen("generic-")
83
4/6
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 64 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 40 times.
✓ Branch 10 taken 24 times.
64 } else if (HasPrefix(name, "generic_", false)) {
84 // Skip the numeric disambiguation suffix and the following '-'.
85 40 size_t pos = 8; // strlen("generic_")
86 40 const size_t start = pos;
87
4/4
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 40 times.
120 while (pos < name.size() &&
88
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 32 times.
56 isdigit(static_cast<unsigned char>(name[pos]))) {
89 24 ++pos;
90 }
91
6/8
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 24 times.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
✓ Branch 8 taken 24 times.
✓ Branch 9 taken 16 times.
40 if (pos == start || pos >= name.size() || name[pos] != '-') {
92 24 return false;
93 }
94
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 rest = name.substr(pos + 1);
95 } else {
96 24 return false;
97 }
98
99 128 return IsIso8601UtcTimestamp(rest);
100 176 }
101
102 /**
103 * Set a generic tag name of the form "generic-YYYY-MM-DDThh:mm:ss.sssZ"
104 */
105 8 void RepositoryTag::SetGenericName() {
106 8 const uint64_t nanoseconds = platform_realtime_ns();
107
108 // Use strftime() to format timestamp to one-second resolution
109 8 const time_t seconds = static_cast<time_t>(nanoseconds / 1000000000);
110 struct tm timestamp;
111 8 gmtime_r(&seconds, &timestamp);
112 char seconds_buffer[32];
113 8 strftime(seconds_buffer, sizeof(seconds_buffer), "generic-%Y-%m-%dT%H:%M:%S",
114 &timestamp);
115
116 // Append milliseconds
117 8 const unsigned offset_milliseconds = ((nanoseconds / 1000000) % 1000);
118 char name_buffer[48];
119 8 snprintf(name_buffer, sizeof(name_buffer), "%s.%03dZ", seconds_buffer,
120 offset_milliseconds);
121
122
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 name_ = std::string(name_buffer);
123 8 }
124