GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2026-08-30 02:40:36
Exec Total Coverage
Lines: 198 216 91.7%
Branches: 125 183 68.3%

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