GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/repository_tag.cc
Date: 2026-06-28 02:36:10
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 314 RepositoryTag::RepositoryTag(const std::string &name,
14 314 const std::string &description)
15 314 : name_(name)
16
1/2
✓ Branch 1 taken 314 times.
✗ Branch 2 not taken.
314 , description_(description)
17
1/2
✓ Branch 2 taken 314 times.
✗ Branch 3 not taken.
314 , delete_tags_("")
18 314 , auto_tag_threshold_(0) { }
19
20 /**
21 * Check if tag name is of the form "generic-*"
22 */
23 5 bool RepositoryTag::HasGenericName() {
24
2/4
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
5 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 16 bool IsIso8601UtcTimestamp(const std::string &s) {
35 // "YYYY-MM-DDTHH:MM:SSZ" without fractional seconds is 20 characters.
36
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 12 times.
16 if (s.size() < 20) {
37 4 return false;
38 }
39
7/10
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 11 times.
✓ Branch 8 taken 1 times.
✓ Branch 10 taken 11 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 11 times.
23 if (s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' ||
40
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
11 s[16] != ':') {
41 1 return false;
42 }
43 11 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 144 times.
✓ Branch 1 taken 10 times.
154 for (size_t k = 0; k < sizeof(digit_pos) / sizeof(digit_pos[0]); ++k) {
45
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 143 times.
144 if (!isdigit(static_cast<unsigned char>(s[digit_pos[k]]))) {
46 1 return false;
47 }
48 }
49 // Optional ".fff" (1-3 digits) follows the seconds.
50 10 size_t pos = 19;
51
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 4 times.
10 if (s[pos] == '.') {
52 6 ++pos;
53 6 size_t fractional = 0;
54
6/6
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 6 times.
38 while (pos < s.size() && fractional < 3 &&
55
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 2 times.
17 isdigit(static_cast<unsigned char>(s[pos]))) {
56 15 ++pos;
57 15 ++fractional;
58 }
59
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 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 7 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
10 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 22 bool RepositoryTag::IsAutoGeneratedName(const std::string &name) {
80 22 std::string rest;
81
4/6
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 22 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 14 times.
✓ Branch 10 taken 8 times.
22 if (HasPrefix(name, "generic-", false)) {
82
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 rest = name.substr(8); // strlen("generic-")
83
4/6
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 5 times.
✓ Branch 10 taken 3 times.
8 } else if (HasPrefix(name, "generic_", false)) {
84 // Skip the numeric disambiguation suffix and the following '-'.
85 5 size_t pos = 8; // strlen("generic_")
86 5 const size_t start = pos;
87
4/4
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 5 times.
15 while (pos < name.size() &&
88
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 4 times.
7 isdigit(static_cast<unsigned char>(name[pos]))) {
89 3 ++pos;
90 }
91
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2 times.
5 if (pos == start || pos >= name.size() || name[pos] != '-') {
92 3 return false;
93 }
94
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 rest = name.substr(pos + 1);
95 } else {
96 3 return false;
97 }
98
99 16 return IsIso8601UtcTimestamp(rest);
100 22 }
101
102 /**
103 * Set a generic tag name of the form "generic-YYYY-MM-DDThh:mm:ss.sssZ"
104 */
105 1 void RepositoryTag::SetGenericName() {
106 1 const uint64_t nanoseconds = platform_realtime_ns();
107
108 // Use strftime() to format timestamp to one-second resolution
109 1 const time_t seconds = static_cast<time_t>(nanoseconds / 1000000000);
110 struct tm timestamp;
111 1 gmtime_r(&seconds, &timestamp);
112 char seconds_buffer[32];
113 1 strftime(seconds_buffer, sizeof(seconds_buffer), "generic-%Y-%m-%dT%H:%M:%S",
114 &timestamp);
115
116 // Append milliseconds
117 1 const unsigned offset_milliseconds = ((nanoseconds / 1000000) % 1000);
118 char name_buffer[48];
119 1 snprintf(name_buffer, sizeof(name_buffer), "%s.%03dZ", seconds_buffer,
120 offset_milliseconds);
121
122
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 name_ = std::string(name_buffer);
123 1 }
124