GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2025-08-31 02:39:21
Exec Total Coverage
Lines: 157 163 96.3%
Branches: 105 151 69.5%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5
6 #include "xattr.h"
7
8 #include <alloca.h>
9 // clang-format off
10 #include <sys/xattr.h>
11 // clang-format on
12
13 #include <cassert>
14 #include <cstring>
15
16 #include "util/platform.h"
17 #include "util/pointer.h"
18 #include "util/smalloc.h"
19 #include "util/string.h"
20
21 using namespace std; // NOLINT
22
23 const uint8_t XattrList::kVersion = 1;
24
25 /**
26 * Converts all the extended attributes of path into a XattrList. Attributes
27 * that violate the XattrList restrictions are ignored. If path does not exist
28 * or on I/O errors, NULL is returned. The list of extended attributes is not
29 * supposed to change during the runtime of this method. The list of values
30 * must not exceed 64kB.
31 */
32 12 XattrList *XattrList::CreateFromFile(const std::string &path) {
33 // Parse the \0 separated list of extended attribute keys
34 char *list;
35 12 ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
36
3/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
12 if ((sz_list < 0) || (sz_list > 64 * 1024)) {
37 3 return NULL;
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 } else if (sz_list == 0) {
39 // No extended attributes
40 return new XattrList();
41 }
42 9 list = reinterpret_cast<char *>(alloca(sz_list));
43 9 sz_list = platform_llistxattr(path.c_str(), list, sz_list);
44
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (sz_list < 0) {
45 return NULL;
46
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 } else if (sz_list == 0) {
47 // Can only happen if the list was removed since the previous call to
48 // llistxattr
49 return new XattrList();
50 }
51
2/4
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
18 vector<string> keys = SplitString(string(list, sz_list), '\0');
52
53 // Retrieve extended attribute values
54
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 XattrList *result = new XattrList();
55 char value[256];
56
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 9 times.
42 for (unsigned i = 0; i < keys.size(); ++i) {
57
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 24 times.
33 if (keys[i].empty())
58 9 continue;
59 24 const ssize_t sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(),
60 value, 256);
61
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 21 times.
24 if (sz_value < 0)
62 3 continue;
63
2/4
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 21 times.
✗ Branch 7 not taken.
21 result->Set(keys[i], string(value, sz_value));
64 }
65 9 return result;
66 9 }
67
68
69 30 XattrList *XattrList::Deserialize(const unsigned char *inbuf,
70 const unsigned size) {
71
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 27 times.
30 if (inbuf == NULL)
72
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 return new XattrList();
73
74
2/4
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 27 times.
✗ Branch 6 not taken.
27 UniquePtr<XattrList> result(new XattrList());
75
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 24 times.
27 if (size < sizeof(XattrHeader))
76 3 return NULL;
77 24 XattrHeader header;
78 24 memcpy(&header, inbuf, sizeof(header));
79
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 21 times.
24 if (header.version != kVersion)
80 3 return NULL;
81 21 unsigned pos = sizeof(header);
82
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 9 times.
42 for (unsigned i = 0; i < header.num_xattrs; ++i) {
83 33 XattrEntry entry;
84 33 unsigned size_preamble = // NOLINT
85 sizeof(entry.len_key) + sizeof(entry.len_value);
86
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 30 times.
33 if (size - pos < size_preamble)
87 12 return NULL;
88 30 memcpy(&entry, inbuf + pos, size_preamble);
89
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 27 times.
30 if (size - pos < entry.GetSize())
90 3 return NULL;
91
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 21 times.
27 if (entry.GetSize() == size_preamble)
92 6 return NULL;
93 21 pos += size_preamble;
94 21 memcpy(entry.data, inbuf + pos, entry.GetSize() - size_preamble);
95 21 pos += entry.GetSize() - size_preamble;
96
3/6
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 21 times.
✗ Branch 9 not taken.
21 const bool retval = result->Set(entry.GetKey(), entry.GetValue());
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 if (!retval)
98 return NULL;
99 }
100 9 return result.Release();
101 27 }
102
103
104 36 bool XattrList::Has(const string &key) const {
105
1/2
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 return xattrs_.find(key) != xattrs_.end();
106 }
107
108
109 813 bool XattrList::Get(const string &key, string *value) const {
110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 813 times.
813 assert(value);
111
1/2
✓ Branch 1 taken 813 times.
✗ Branch 2 not taken.
813 const map<string, string>::const_iterator iter = xattrs_.find(key);
112
2/2
✓ Branch 2 taken 807 times.
✓ Branch 3 taken 6 times.
813 if (iter != xattrs_.end()) {
113
1/2
✓ Branch 2 taken 807 times.
✗ Branch 3 not taken.
807 *value = iter->second;
114 807 return true;
115 }
116 6 return false;
117 }
118
119
120 39 vector<string> XattrList::ListKeys() const {
121 39 vector<string> result;
122 78 for (map<string, string>::const_iterator i = xattrs_.begin(),
123 39 iEnd = xattrs_.end();
124
2/2
✓ Branch 1 taken 846 times.
✓ Branch 2 taken 39 times.
885 i != iEnd;
125 846 ++i) {
126
1/2
✓ Branch 2 taken 846 times.
✗ Branch 3 not taken.
846 result.push_back(i->first);
127 }
128 39 return result;
129 }
130
131
132 /**
133 * The format of extended attribute lists in the (l)listxattr call is an array
134 * of all the keys concatenated and separated by null characters. If merge_with
135 * is not empty, the final list will be have the keys from the XattrList and the
136 * keys from merge_with without duplicates. The merge_with list is supposed to
137 * be in POSIX format.
138 */
139 12 string XattrList::ListKeysPosix(const string &merge_with) const {
140 12 string result;
141
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
12 if (!merge_with.empty()) {
142
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 vector<string> merge_list = SplitString(merge_with, '\0');
143
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 6 times.
30 for (unsigned i = 0; i < merge_list.size(); ++i) {
144
2/2
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 18 times.
24 if (merge_list[i].empty())
145 6 continue;
146
3/5
✓ Branch 3 taken 18 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✓ Branch 7 taken 3 times.
18 if (xattrs_.find(merge_list[i]) == xattrs_.end()) {
147
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 result += merge_list[i];
148
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 result.push_back('\0');
149 }
150 }
151 6 }
152 24 for (map<string, string>::const_iterator i = xattrs_.begin(),
153 12 iEnd = xattrs_.end();
154
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 12 times.
30 i != iEnd;
155 18 ++i) {
156
1/2
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
18 result += i->first;
157
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 result.push_back('\0');
158 }
159 12 return result;
160 }
161
162
163 1722 bool XattrList::Set(const string &key, const string &value) {
164
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1722 times.
1722 if (key.empty())
165 return false;
166
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1719 times.
1722 if (key.length() > 256)
167 3 return false;
168
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1716 times.
1719 if (key.find('\0') != string::npos)
169 3 return false;
170
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1713 times.
1716 if (value.length() > 256)
171 3 return false;
172
173
1/2
✓ Branch 1 taken 1713 times.
✗ Branch 2 not taken.
1713 const map<string, string>::iterator iter = xattrs_.find(key);
174
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1710 times.
1713 if (iter != xattrs_.end()) {
175
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 iter->second = value;
176 } else {
177
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1707 times.
1710 if (xattrs_.size() >= 256)
178 3 return false;
179
2/4
✓ Branch 1 taken 1707 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1707 times.
✗ Branch 5 not taken.
1707 xattrs_[key] = value;
180 }
181 1710 return true;
182 }
183
184
185 15 bool XattrList::Remove(const string &key) {
186
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 const map<string, string>::iterator iter = xattrs_.find(key);
187
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 3 times.
15 if (iter != xattrs_.end()) {
188
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 xattrs_.erase(iter);
189 12 return true;
190 }
191 3 return false;
192 }
193
194
195 /**
196 * If the list of attributes is empty, Serialize returns NULL. Deserialize
197 * can deal with NULL pointers.
198 */
199 15 void XattrList::Serialize(unsigned char **outbuf,
200 unsigned *size,
201 const std::vector<std::string> *blacklist) const {
202
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 12 times.
15 if (xattrs_.empty()) {
203 3 *size = 0;
204 3 *outbuf = NULL;
205 6 return;
206 }
207
208 12 XattrHeader header(xattrs_.size());
209 12 uint32_t packed_size = sizeof(header);
210
211 // Determine size of the buffer (allocate space for max num of attributes)
212 XattrEntry *entries = reinterpret_cast<XattrEntry *>(
213 12 smalloc(header.num_xattrs * sizeof(XattrEntry)));
214 12 unsigned ientries = 0;
215 24 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
216 12 it_att_end = xattrs_.end();
217
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 12 times.
48 it_att != it_att_end;
218 36 ++it_att) {
219 // Only serialize non-blacklist items
220
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 18 times.
36 if (blacklist != NULL) {
221 18 bool skip = false;
222
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 3 times.
36 for (unsigned i_bl = 0; i_bl < blacklist->size(); ++i_bl) {
223
3/4
✓ Branch 3 taken 33 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 18 times.
33 if (HasPrefix(it_att->first, (*blacklist)[i_bl],
224 true /* ignore_case */)) {
225 15 skip = true;
226 15 break;
227 }
228 }
229
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3 times.
18 if (skip)
230 15 continue;
231 }
232 /*entries[ientries] =*/
233 21 new (entries + ientries) XattrEntry(it_att->first, it_att->second);
234 21 packed_size += entries[ientries].GetSize();
235 21 ientries++;
236 }
237
238 // We might have skipped all attributes
239
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (ientries == 0) {
240 3 free(entries);
241 3 *size = 0;
242 3 *outbuf = NULL;
243 3 return;
244 }
245
246 // Copy data into buffer
247 9 header.num_xattrs = ientries;
248 9 *size = packed_size;
249 9 *outbuf = reinterpret_cast<unsigned char *>(smalloc(packed_size));
250 9 memcpy(*outbuf, &header, sizeof(header));
251 9 unsigned pos = sizeof(header);
252
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 9 times.
30 for (unsigned i = 0; i < header.num_xattrs; ++i) {
253 21 memcpy(*outbuf + pos, &entries[i], entries[i].GetSize());
254 21 pos += entries[i].GetSize();
255 }
256
257 9 free(entries);
258 }
259
260
261 //------------------------------------------------------------------------------
262
263
264 21 string XattrList::XattrEntry::GetKey() const {
265
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 if (len_key == 0)
266 return "";
267
1/2
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
21 return string(data, len_key);
268 }
269
270
271 162 uint16_t XattrList::XattrEntry::GetSize() const {
272 162 return sizeof(len_key) + sizeof(len_value) + uint16_t(len_key)
273 162 + uint16_t(len_value);
274 }
275
276
277 21 string XattrList::XattrEntry::GetValue() const {
278
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 15 times.
21 if (len_value == 0)
279
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 return "";
280
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 return string(&data[len_key], len_value);
281 }
282
283
284 21 XattrList::XattrEntry::XattrEntry(const string &key, const string &value)
285 21 : len_key(key.size()), len_value(value.size()) {
286 21 memcpy(data, key.data(), len_key);
287 21 memcpy(data + len_key, value.data(), len_value);
288 21 }
289