GCC Code Coverage Report


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