GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2026-05-19 11:45:12
Exec Total Coverage
Lines: 196 214 91.6%
Branches: 126 185 68.1%

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::kVersionSmall = 1;
24 const uint8_t XattrList::kVersionBig = 2; // As of cvmfs 2.14
25
26 /**
27 * Converts all the extended attributes of path into a XattrList. Attributes
28 * that violate the XattrList restrictions are ignored. If path does not exist
29 * or on I/O errors, NULL is returned. The list of extended attributes is not
30 * supposed to change during the runtime of this method. The list of values
31 * must not exceed 64kB.
32 */
33 4 XattrList *XattrList::CreateFromFile(const std::string &path) {
34 // Parse the \0 separated list of extended attribute keys
35 char *list;
36 4 ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
37
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
4 if ((sz_list < 0) || (sz_list > 64 * 1024)) {
38 1 return NULL;
39
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (sz_list == 0) {
40 // No extended attributes
41 return new XattrList();
42 }
43 3 list = reinterpret_cast<char *>(alloca(sz_list));
44 3 sz_list = platform_llistxattr(path.c_str(), list, sz_list);
45
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sz_list < 0) {
46 return NULL;
47
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (sz_list == 0) {
48 // Can only happen if the list was removed since the previous call to
49 // llistxattr
50 return new XattrList();
51 }
52
2/4
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
6 vector<string> keys = SplitString(string(list, sz_list), '\0');
53
54 // Retrieve extended attribute values
55
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 XattrList *result = new XattrList();
56 char value_smallbuf[255];
57
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 3 times.
14 for (unsigned i = 0; i < keys.size(); ++i) {
58
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 8 times.
11 if (keys[i].empty())
59 3 continue;
60
61 8 char *buffer = value_smallbuf;
62 8 size_t sz_buffer = 255;
63 8 ssize_t sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(), buffer,
64 sz_buffer);
65 // check if we need to allocate bigger buffer
66
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
8 if ((sz_value < 0) && (errno == ERANGE)) {
67 // query lgetxattr with size 0 to get proper buffer size
68 1 sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(), NULL, 0);
69 1 sz_buffer = sz_value;
70 1 buffer = reinterpret_cast<char *>(smalloc(sz_buffer));
71 1 sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(), buffer,
72 sz_buffer);
73 }
74
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (sz_value >= 0)
75
2/4
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
8 result->Set(keys[i], string(buffer, sz_value));
76
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (buffer != value_smallbuf)
77 1 free(buffer);
78 }
79 3 return result;
80 3 }
81
82
83 11 XattrList *XattrList::Deserialize(const unsigned char *inbuf,
84 const unsigned size) {
85
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (inbuf == NULL)
86
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 return new XattrList();
87
88
2/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 10 times.
✗ Branch 6 not taken.
10 UniquePtr<XattrList> result(new XattrList());
89
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (size < sizeof(XattrHeader))
90 1 return NULL;
91 9 XattrHeader header;
92 9 memcpy(&header, inbuf, sizeof(header));
93
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 8 times.
9 if (!IsSupportedVersion(header.version))
94 1 return NULL;
95
96 8 XattrEntrySerializer entry_serializer(header.version);
97 8 unsigned char *bufpos = const_cast<unsigned char *>(inbuf);
98 8 unsigned remain = size;
99 8 bufpos += sizeof(XattrHeader);
100 8 remain -= sizeof(XattrHeader);
101
102
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 4 times.
19 for (unsigned i = 0; i < header.num_xattrs; ++i) {
103 15 std::string key;
104 15 std::string value;
105 const uint32_t nbytes =
106
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 entry_serializer.Deserialize(bufpos, remain, &key, &value);
107
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (nbytes == 0)
108 2 return NULL;
109
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 const bool retval = result->Set(key, value);
110
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
13 if (!retval)
111 2 return NULL;
112
113 11 remain -= nbytes;
114 11 bufpos += nbytes;
115
4/4
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 4 times.
19 }
116 4 return result.Release();
117 10 }
118
119
120 13 bool XattrList::Has(const string &key) const {
121
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 return xattrs_.find(key) != xattrs_.end();
122 }
123
124
125 272 bool XattrList::Get(const string &key, string *value) const {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 272 times.
272 assert(value);
127
1/2
✓ Branch 1 taken 272 times.
✗ Branch 2 not taken.
272 const map<string, string>::const_iterator iter = xattrs_.find(key);
128
2/2
✓ Branch 2 taken 270 times.
✓ Branch 3 taken 2 times.
272 if (iter != xattrs_.end()) {
129
1/2
✓ Branch 2 taken 270 times.
✗ Branch 3 not taken.
270 *value = iter->second;
130 270 return true;
131 }
132 2 return false;
133 }
134
135
136 17 vector<string> XattrList::ListKeys() const {
137 17 vector<string> result;
138 34 for (map<string, string>::const_iterator i = xattrs_.begin(),
139 17 iEnd = xattrs_.end();
140
2/2
✓ Branch 1 taken 302 times.
✓ Branch 2 taken 17 times.
319 i != iEnd;
141 302 ++i) {
142
1/2
✓ Branch 2 taken 302 times.
✗ Branch 3 not taken.
302 result.push_back(i->first);
143 }
144 17 return result;
145 }
146
147
148 /**
149 * The format of extended attribute lists in the (l)listxattr call is an array
150 * of all the keys concatenated and separated by null characters. If merge_with
151 * is not empty, the final list will be have the keys from the XattrList and the
152 * keys from merge_with without duplicates. The merge_with list is supposed to
153 * be in POSIX format.
154 */
155 4 string XattrList::ListKeysPosix(const string &merge_with) const {
156 4 string result;
157
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if (!merge_with.empty()) {
158
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 vector<string> merge_list = SplitString(merge_with, '\0');
159
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
10 for (unsigned i = 0; i < merge_list.size(); ++i) {
160
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6 times.
8 if (merge_list[i].empty())
161 2 continue;
162
3/5
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 1 times.
6 if (xattrs_.find(merge_list[i]) == xattrs_.end()) {
163
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 result += merge_list[i];
164
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 result.push_back('\0');
165 }
166 }
167 2 }
168 8 for (map<string, string>::const_iterator i = xattrs_.begin(),
169 4 iEnd = xattrs_.end();
170
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 4 times.
12 i != iEnd;
171 8 ++i) {
172
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 result += i->first;
173
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 result.push_back('\0');
174 }
175 4 return result;
176 }
177
178
179 599 bool XattrList::Set(const string &key, const string &value) {
180
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 597 times.
599 if (key.empty())
181 2 return false;
182
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 596 times.
597 if (key.length() > 255)
183 1 return false;
184
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 595 times.
596 if (key.find('\0') != string::npos)
185 1 return false;
186
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 594 times.
595 if (value.length() >= 64 * 1024)
187 1 return false;
188
189
1/2
✓ Branch 1 taken 594 times.
✗ Branch 2 not taken.
594 const map<string, string>::iterator iter = xattrs_.find(key);
190
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 593 times.
594 if (iter != xattrs_.end()) {
191
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 iter->second = value;
192 } else {
193
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 592 times.
593 if (xattrs_.size() >= 256)
194 1 return false;
195
2/4
✓ Branch 1 taken 592 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 592 times.
✗ Branch 5 not taken.
592 xattrs_[key] = value;
196 }
197 593 return true;
198 }
199
200
201 6 bool XattrList::Remove(const string &key) {
202
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const map<string, string>::iterator iter = xattrs_.find(key);
203
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
6 if (iter != xattrs_.end()) {
204
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 xattrs_.erase(iter);
205 5 return true;
206 }
207 1 return false;
208 }
209
210
211 /**
212 * If the list of attributes is empty, Serialize returns NULL. Deserialize
213 * can deal with NULL pointers.
214 */
215 6 void XattrList::Serialize(unsigned char **outbuf,
216 unsigned *size,
217 const std::vector<std::string> *blacklist) const {
218
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 5 times.
6 if (xattrs_.empty()) {
219 1 *size = 0;
220 1 *outbuf = NULL;
221 1 return;
222 }
223
224 5 XattrHeader header;
225 5 *size = sizeof(header);
226
227 10 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
228
2/2
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 5 times.
27 it_att_end = xattrs_.end(); it_att != it_att_end; ++it_att)
229 {
230 17 *size += it_att->first.length();
231 17 *size += it_att->second.length();
232
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 13 times.
17 if (it_att->second.length() > 255)
233 4 header.version = kVersionBig;
234 }
235
236 5 XattrEntrySerializer entry_serializer(header.version);
237 5 *size += xattrs_.size() * entry_serializer.GetHeaderSize();
238 5 *outbuf = reinterpret_cast<unsigned char *>(smalloc(*size));
239 5 unsigned char *bufpos = *outbuf;
240
241 // We copy the header at the end when we know the actual number of entries
242 5 bufpos += sizeof(header);
243
244 5 header.num_xattrs = 0;
245 10 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
246 5 it_att_end = xattrs_.end();
247
2/2
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 5 times.
22 it_att != it_att_end;
248 17 ++it_att) {
249 // Only serialize non-blacklist items
250
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 9 times.
17 if (blacklist != NULL) {
251 8 bool skip = false;
252
2/2
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 2 times.
19 for (unsigned i_bl = 0; i_bl < blacklist->size(); ++i_bl) {
253
3/4
✓ Branch 3 taken 17 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 11 times.
17 if (HasPrefix(it_att->first, (*blacklist)[i_bl],
254 true /* ignore_case */)) {
255 6 skip = true;
256 6 break;
257 }
258 }
259
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (skip)
260 6 continue;
261 }
262
263 11 bufpos += entry_serializer.Serialize(it_att->first, it_att->second, bufpos);
264 11 header.num_xattrs++;
265 }
266
267 // We might have skipped all attributes
268
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (header.num_xattrs == 0) {
269 1 free(*outbuf);
270 1 *size = 0;
271 1 *outbuf = NULL;
272 } else {
273 4 memcpy(*outbuf, &header, sizeof(header));
274 }
275 }
276
277
278 //------------------------------------------------------------------------------
279
280 13 XattrList::XattrEntrySerializer::XattrEntrySerializer(uint8_t version)
281 13 : version_(version)
282 {
283
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
13 assert(version_ == kVersionSmall || version_ == kVersionBig);
284 13 }
285
286 11 uint32_t XattrList::XattrEntrySerializer::Serialize(
287 const std::string &key, const std::string &value, unsigned char *to)
288 {
289
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
11 assert(key.size() < 256);
290
3/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
11 assert(value.size() < ((version_ == kVersionSmall) ? 256 : 64 * 1024));
291
292 11 const uint8_t len_key = key.size();
293 11 memcpy(to, &len_key, 1);
294 11 to += 1;
295
296
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (version_ == kVersionSmall) {
297 1 const uint8_t len_value = value.size();
298 1 memcpy(to, &len_value, 1);
299 1 to += 1;
300 } else {
301
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 assert(version_ == kVersionBig);
302 10 const uint16_t len_value = platform_htole16(value.size());
303 10 memcpy(to, &len_value, 2);
304 10 to += 2;
305 }
306
307 11 memcpy(to, key.data(), key.size());
308 11 to += key.size();
309 11 memcpy(to, value.data(), value.size());
310 11 to += value.size();
311
312 11 return GetHeaderSize() + key.size() + value.size();
313 }
314
315 15 uint32_t XattrList::XattrEntrySerializer::Deserialize(
316 const unsigned char *from, uint32_t bufsize,
317 std::string *key, std::string *value)
318 {
319
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 14 times.
15 if (bufsize < GetHeaderSize())
320 1 return 0;
321 14 bufsize -= GetHeaderSize();
322
323 uint8_t len_key;
324 14 memcpy(&len_key, from, 1);
325
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 key->resize(len_key);
326 14 from += 1;
327
328
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (version_ == kVersionSmall) {
329 uint8_t len_value;
330 1 memcpy(&len_value, from, 1);
331
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 value->resize(len_value);
332 1 from += 1;
333 } else {
334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 assert(version_ == kVersionBig);
335 uint16_t len_value;
336 13 memcpy(&len_value, from, 2);
337
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 value->resize(platform_le16toh(len_value));
338 13 from += 2;
339 }
340
341
342
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 13 times.
14 if (bufsize < key->size() + value->size())
343 1 return 0;
344
345 13 memcpy(const_cast<char *>(key->data()), from, key->size());
346 13 from += key->size();
347 13 memcpy(const_cast<char *>(value->data()), from, value->size());
348
349 13 return GetHeaderSize() + key->size() + value->size();
350 }
351
352 string XattrList::XattrEntry::GetKey() const {
353 if (len_key == 0)
354 return "";
355 return string(data, len_key);
356 }
357
358
359 uint16_t XattrList::XattrEntry::GetSize() const {
360 return sizeof(len_key) + sizeof(len_value) + uint16_t(len_key)
361 + uint16_t(len_value);
362 }
363
364
365 string XattrList::XattrEntry::GetValue() const {
366 if (len_value == 0)
367 return "";
368 return string(&data[len_key], len_value);
369 }
370
371
372 XattrList::XattrEntry::XattrEntry(const string &key, const string &value)
373 : len_key(key.size()), len_value(value.size()) {
374 memcpy(data, key.data(), len_key);
375 memcpy(data + len_key, value.data(), len_value);
376 }
377