GCC Code Coverage Report


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