GCC Code Coverage Report


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