GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2026-04-26 02:35:59
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 12 XattrList *XattrList::CreateFromFile(const std::string &path) {
34 // Parse the \0 separated list of extended attribute keys
35 char *list;
36 12 ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
37
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)) {
38 3 return NULL;
39
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 } else if (sz_list == 0) {
40 // No extended attributes
41 return new XattrList();
42 }
43 9 list = reinterpret_cast<char *>(alloca(sz_list));
44 9 sz_list = platform_llistxattr(path.c_str(), list, sz_list);
45
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (sz_list < 0) {
46 return NULL;
47
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 } 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 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');
53
54 // Retrieve extended attribute values
55
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 XattrList *result = new XattrList();
56 char value_smallbuf[255];
57
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 9 times.
42 for (unsigned i = 0; i < keys.size(); ++i) {
58
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 24 times.
33 if (keys[i].empty())
59 9 continue;
60
61 24 char *buffer = value_smallbuf;
62 24 size_t sz_buffer = 255;
63 24 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 3 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
24 if ((sz_value < 0) && (errno == ERANGE)) {
67 // query lgetxattr with size 0 to get proper buffer size
68 3 sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(), NULL, 0);
69 3 sz_buffer = sz_value;
70 3 buffer = reinterpret_cast<char *>(smalloc(sz_buffer));
71 3 sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(), buffer,
72 sz_buffer);
73 }
74
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if (sz_value >= 0)
75
2/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
24 result->Set(keys[i], string(buffer, sz_value));
76
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 21 times.
24 if (buffer != value_smallbuf)
77 3 free(buffer);
78 }
79 9 return result;
80 9 }
81
82
83 33 XattrList *XattrList::Deserialize(const unsigned char *inbuf,
84 const unsigned size) {
85
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 30 times.
33 if (inbuf == NULL)
86
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 return new XattrList();
87
88
2/4
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 30 times.
✗ Branch 6 not taken.
30 UniquePtr<XattrList> result(new XattrList());
89
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 27 times.
30 if (size < sizeof(XattrHeader))
90 3 return NULL;
91 27 XattrHeader header;
92 27 memcpy(&header, inbuf, sizeof(header));
93
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 24 times.
27 if (!IsSupportedVersion(header.version))
94 3 return NULL;
95
96 24 XattrEntrySerializer entry_serializer(header.version);
97 24 unsigned char *bufpos = const_cast<unsigned char *>(inbuf);
98 24 unsigned remain = size;
99 24 bufpos += sizeof(XattrHeader);
100 24 remain -= sizeof(XattrHeader);
101
102
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 12 times.
57 for (unsigned i = 0; i < header.num_xattrs; ++i) {
103 45 std::string key;
104 45 std::string value;
105 const uint32_t nbytes =
106
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 entry_serializer.Deserialize(bufpos, remain, &key, &value);
107
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 39 times.
45 if (nbytes == 0)
108 6 return NULL;
109
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 const bool retval = result->Set(key, value);
110
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 33 times.
39 if (!retval)
111 6 return NULL;
112
113 33 remain -= nbytes;
114 33 bufpos += nbytes;
115
4/4
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 12 times.
✓ Branch 4 taken 33 times.
✓ Branch 5 taken 12 times.
57 }
116 12 return result.Release();
117 30 }
118
119
120 39 bool XattrList::Has(const string &key) const {
121
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 return xattrs_.find(key) != xattrs_.end();
122 }
123
124
125 816 bool XattrList::Get(const string &key, string *value) const {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 816 times.
816 assert(value);
127
1/2
✓ Branch 1 taken 816 times.
✗ Branch 2 not taken.
816 const map<string, string>::const_iterator iter = xattrs_.find(key);
128
2/2
✓ Branch 2 taken 810 times.
✓ Branch 3 taken 6 times.
816 if (iter != xattrs_.end()) {
129
1/2
✓ Branch 2 taken 810 times.
✗ Branch 3 not taken.
810 *value = iter->second;
130 810 return true;
131 }
132 6 return false;
133 }
134
135
136 51 vector<string> XattrList::ListKeys() const {
137 51 vector<string> result;
138 102 for (map<string, string>::const_iterator i = xattrs_.begin(),
139 51 iEnd = xattrs_.end();
140
2/2
✓ Branch 1 taken 906 times.
✓ Branch 2 taken 51 times.
957 i != iEnd;
141 906 ++i) {
142
1/2
✓ Branch 2 taken 906 times.
✗ Branch 3 not taken.
906 result.push_back(i->first);
143 }
144 51 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 12 string XattrList::ListKeysPosix(const string &merge_with) const {
156 12 string result;
157
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
12 if (!merge_with.empty()) {
158
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 vector<string> merge_list = SplitString(merge_with, '\0');
159
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 6 times.
30 for (unsigned i = 0; i < merge_list.size(); ++i) {
160
2/2
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 18 times.
24 if (merge_list[i].empty())
161 6 continue;
162
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()) {
163
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 result += merge_list[i];
164
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 result.push_back('\0');
165 }
166 }
167 6 }
168 24 for (map<string, string>::const_iterator i = xattrs_.begin(),
169 12 iEnd = xattrs_.end();
170
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 12 times.
36 i != iEnd;
171 24 ++i) {
172
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 result += i->first;
173
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 result.push_back('\0');
174 }
175 12 return result;
176 }
177
178
179 1797 bool XattrList::Set(const string &key, const string &value) {
180
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 1791 times.
1797 if (key.empty())
181 6 return false;
182
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1788 times.
1791 if (key.length() > 255)
183 3 return false;
184
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1785 times.
1788 if (key.find('\0') != string::npos)
185 3 return false;
186
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1782 times.
1785 if (value.length() >= 64 * 1024)
187 3 return false;
188
189
1/2
✓ Branch 1 taken 1782 times.
✗ Branch 2 not taken.
1782 const map<string, string>::iterator iter = xattrs_.find(key);
190
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1779 times.
1782 if (iter != xattrs_.end()) {
191
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 iter->second = value;
192 } else {
193
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1776 times.
1779 if (xattrs_.size() >= 256)
194 3 return false;
195
2/4
✓ Branch 1 taken 1776 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1776 times.
✗ Branch 5 not taken.
1776 xattrs_[key] = value;
196 }
197 1779 return true;
198 }
199
200
201 18 bool XattrList::Remove(const string &key) {
202
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 const map<string, string>::iterator iter = xattrs_.find(key);
203
2/2
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 3 times.
18 if (iter != xattrs_.end()) {
204
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 xattrs_.erase(iter);
205 15 return true;
206 }
207 3 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 18 void XattrList::Serialize(unsigned char **outbuf,
216 unsigned *size,
217 const std::vector<std::string> *blacklist) const {
218
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 15 times.
18 if (xattrs_.empty()) {
219 3 *size = 0;
220 3 *outbuf = NULL;
221 3 return;
222 }
223
224 15 XattrHeader header;
225 15 *size = sizeof(header);
226
227 30 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
228
2/2
✓ Branch 3 taken 51 times.
✓ Branch 4 taken 15 times.
81 it_att_end = xattrs_.end(); it_att != it_att_end; ++it_att)
229 {
230 51 *size += it_att->first.length();
231 51 *size += it_att->second.length();
232
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 39 times.
51 if (it_att->second.length() > 255)
233 12 header.version = kVersionBig;
234 }
235
236 15 XattrEntrySerializer entry_serializer(header.version);
237 15 *size += xattrs_.size() * entry_serializer.GetHeaderSize();
238 15 *outbuf = reinterpret_cast<unsigned char *>(smalloc(*size));
239 15 unsigned char *bufpos = *outbuf;
240
241 // We copy the header at the end when we know the actual number of entries
242 15 bufpos += sizeof(header);
243
244 15 header.num_xattrs = 0;
245 30 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
246 15 it_att_end = xattrs_.end();
247
2/2
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 15 times.
66 it_att != it_att_end;
248 51 ++it_att) {
249 // Only serialize non-blacklist items
250
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 27 times.
51 if (blacklist != NULL) {
251 24 bool skip = false;
252
2/2
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 6 times.
57 for (unsigned i_bl = 0; i_bl < blacklist->size(); ++i_bl) {
253
3/4
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 33 times.
51 if (HasPrefix(it_att->first, (*blacklist)[i_bl],
254 true /* ignore_case */)) {
255 18 skip = true;
256 18 break;
257 }
258 }
259
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 6 times.
24 if (skip)
260 18 continue;
261 }
262
263 33 bufpos += entry_serializer.Serialize(it_att->first, it_att->second, bufpos);
264 33 header.num_xattrs++;
265 }
266
267 // We might have skipped all attributes
268
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12 times.
15 if (header.num_xattrs == 0) {
269 3 free(*outbuf);
270 3 *size = 0;
271 3 *outbuf = NULL;
272 } else {
273 12 memcpy(*outbuf, &header, sizeof(header));
274 }
275 }
276
277
278 //------------------------------------------------------------------------------
279
280 39 XattrList::XattrEntrySerializer::XattrEntrySerializer(uint8_t version)
281 39 : version_(version)
282 {
283
3/4
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 33 times.
39 assert(version_ == kVersionSmall || version_ == kVersionBig);
284 39 }
285
286 33 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 33 times.
33 assert(key.size() < 256);
290
3/4
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 33 times.
33 assert(value.size() < ((version_ == kVersionSmall) ? 256 : 64 * 1024));
291
292 33 const uint8_t len_key = key.size();
293 33 memcpy(to, &len_key, 1);
294 33 to += 1;
295
296
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 30 times.
33 if (version_ == kVersionSmall) {
297 3 const uint8_t len_value = value.size();
298 3 memcpy(to, &len_value, 1);
299 3 to += 1;
300 } else {
301
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 assert(version_ == kVersionBig);
302 30 const uint16_t len_value = platform_htole16(value.size());
303 30 memcpy(to, &len_value, 2);
304 30 to += 2;
305 }
306
307 33 memcpy(to, key.data(), key.size());
308 33 to += key.size();
309 33 memcpy(to, value.data(), value.size());
310 33 to += value.size();
311
312 33 return GetHeaderSize() + key.size() + value.size();
313 }
314
315 45 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 3 times.
✓ Branch 2 taken 42 times.
45 if (bufsize < GetHeaderSize())
320 3 return 0;
321 42 bufsize -= GetHeaderSize();
322
323 uint8_t len_key;
324 42 memcpy(&len_key, from, 1);
325
1/2
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
42 key->resize(len_key);
326 42 from += 1;
327
328
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 39 times.
42 if (version_ == kVersionSmall) {
329 uint8_t len_value;
330 3 memcpy(&len_value, from, 1);
331
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 value->resize(len_value);
332 3 from += 1;
333 } else {
334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 assert(version_ == kVersionBig);
335 uint16_t len_value;
336 39 memcpy(&len_value, from, 2);
337
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 value->resize(platform_le16toh(len_value));
338 39 from += 2;
339 }
340
341
342
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 39 times.
42 if (bufsize < key->size() + value->size())
343 3 return 0;
344
345 39 memcpy(const_cast<char *>(key->data()), from, key->size());
346 39 from += key->size();
347 39 memcpy(const_cast<char *>(value->data()), from, value->size());
348
349 39 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