GCC Code Coverage Report


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