GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2026-02-22 02:35:58
Exec Total Coverage
Lines: 197 216 91.2%
Branches: 127 187 67.9%

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