GCC Code Coverage Report


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