GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2025-09-28 02:35:26
Exec Total Coverage
Lines: 157 163 96.3%
Branches: 105 151 69.5%

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::kVersion = 1;
24
25 /**
26 * Converts all the extended attributes of path into a XattrList. Attributes
27 * that violate the XattrList restrictions are ignored. If path does not exist
28 * or on I/O errors, NULL is returned. The list of extended attributes is not
29 * supposed to change during the runtime of this method. The list of values
30 * must not exceed 64kB.
31 */
32 164 XattrList *XattrList::CreateFromFile(const std::string &path) {
33 // Parse the \0 separated list of extended attribute keys
34 char *list;
35 164 ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
36
3/4
✓ Branch 0 taken 123 times.
✓ Branch 1 taken 41 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 123 times.
164 if ((sz_list < 0) || (sz_list > 64 * 1024)) {
37 41 return NULL;
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123 times.
123 } else if (sz_list == 0) {
39 // No extended attributes
40 return new XattrList();
41 }
42 123 list = reinterpret_cast<char *>(alloca(sz_list));
43 123 sz_list = platform_llistxattr(path.c_str(), list, sz_list);
44
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123 times.
123 if (sz_list < 0) {
45 return NULL;
46
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123 times.
123 } else if (sz_list == 0) {
47 // Can only happen if the list was removed since the previous call to
48 // llistxattr
49 return new XattrList();
50 }
51
2/4
✓ Branch 2 taken 123 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 123 times.
✗ Branch 6 not taken.
246 vector<string> keys = SplitString(string(list, sz_list), '\0');
52
53 // Retrieve extended attribute values
54
1/2
✓ Branch 1 taken 123 times.
✗ Branch 2 not taken.
123 XattrList *result = new XattrList();
55 char value[256];
56
2/2
✓ Branch 1 taken 451 times.
✓ Branch 2 taken 123 times.
574 for (unsigned i = 0; i < keys.size(); ++i) {
57
2/2
✓ Branch 2 taken 123 times.
✓ Branch 3 taken 328 times.
451 if (keys[i].empty())
58 123 continue;
59 328 const ssize_t sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(),
60 value, 256);
61
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 287 times.
328 if (sz_value < 0)
62 41 continue;
63
2/4
✓ Branch 2 taken 287 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 287 times.
✗ Branch 7 not taken.
287 result->Set(keys[i], string(value, sz_value));
64 }
65 123 return result;
66 123 }
67
68
69 410 XattrList *XattrList::Deserialize(const unsigned char *inbuf,
70 const unsigned size) {
71
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 369 times.
410 if (inbuf == NULL)
72
1/2
✓ Branch 1 taken 41 times.
✗ Branch 2 not taken.
41 return new XattrList();
73
74
2/4
✓ Branch 1 taken 369 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 369 times.
✗ Branch 6 not taken.
369 UniquePtr<XattrList> result(new XattrList());
75
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 328 times.
369 if (size < sizeof(XattrHeader))
76 41 return NULL;
77 328 XattrHeader header;
78 328 memcpy(&header, inbuf, sizeof(header));
79
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 287 times.
328 if (header.version != kVersion)
80 41 return NULL;
81 287 unsigned pos = sizeof(header);
82
2/2
✓ Branch 0 taken 451 times.
✓ Branch 1 taken 123 times.
574 for (unsigned i = 0; i < header.num_xattrs; ++i) {
83 451 XattrEntry entry;
84 451 unsigned size_preamble = // NOLINT
85 sizeof(entry.len_key) + sizeof(entry.len_value);
86
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 410 times.
451 if (size - pos < size_preamble)
87 164 return NULL;
88 410 memcpy(&entry, inbuf + pos, size_preamble);
89
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 369 times.
410 if (size - pos < entry.GetSize())
90 41 return NULL;
91
2/2
✓ Branch 1 taken 82 times.
✓ Branch 2 taken 287 times.
369 if (entry.GetSize() == size_preamble)
92 82 return NULL;
93 287 pos += size_preamble;
94 287 memcpy(entry.data, inbuf + pos, entry.GetSize() - size_preamble);
95 287 pos += entry.GetSize() - size_preamble;
96
3/6
✓ Branch 2 taken 287 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 287 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 287 times.
✗ Branch 9 not taken.
287 const bool retval = result->Set(entry.GetKey(), entry.GetValue());
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 287 times.
287 if (!retval)
98 return NULL;
99 }
100 123 return result.Release();
101 369 }
102
103
104 492 bool XattrList::Has(const string &key) const {
105
1/2
✓ Branch 2 taken 492 times.
✗ Branch 3 not taken.
492 return xattrs_.find(key) != xattrs_.end();
106 }
107
108
109 11111 bool XattrList::Get(const string &key, string *value) const {
110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11111 times.
11111 assert(value);
111
1/2
✓ Branch 1 taken 11111 times.
✗ Branch 2 not taken.
11111 const map<string, string>::const_iterator iter = xattrs_.find(key);
112
2/2
✓ Branch 2 taken 11029 times.
✓ Branch 3 taken 82 times.
11111 if (iter != xattrs_.end()) {
113
1/2
✓ Branch 2 taken 11029 times.
✗ Branch 3 not taken.
11029 *value = iter->second;
114 11029 return true;
115 }
116 82 return false;
117 }
118
119
120 533 vector<string> XattrList::ListKeys() const {
121 533 vector<string> result;
122 1066 for (map<string, string>::const_iterator i = xattrs_.begin(),
123 533 iEnd = xattrs_.end();
124
2/2
✓ Branch 1 taken 11562 times.
✓ Branch 2 taken 533 times.
12095 i != iEnd;
125 11562 ++i) {
126
1/2
✓ Branch 2 taken 11562 times.
✗ Branch 3 not taken.
11562 result.push_back(i->first);
127 }
128 533 return result;
129 }
130
131
132 /**
133 * The format of extended attribute lists in the (l)listxattr call is an array
134 * of all the keys concatenated and separated by null characters. If merge_with
135 * is not empty, the final list will be have the keys from the XattrList and the
136 * keys from merge_with without duplicates. The merge_with list is supposed to
137 * be in POSIX format.
138 */
139 164 string XattrList::ListKeysPosix(const string &merge_with) const {
140 164 string result;
141
2/2
✓ Branch 1 taken 82 times.
✓ Branch 2 taken 82 times.
164 if (!merge_with.empty()) {
142
1/2
✓ Branch 1 taken 82 times.
✗ Branch 2 not taken.
82 vector<string> merge_list = SplitString(merge_with, '\0');
143
2/2
✓ Branch 1 taken 328 times.
✓ Branch 2 taken 82 times.
410 for (unsigned i = 0; i < merge_list.size(); ++i) {
144
2/2
✓ Branch 2 taken 82 times.
✓ Branch 3 taken 246 times.
328 if (merge_list[i].empty())
145 82 continue;
146
3/5
✓ Branch 3 taken 246 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 205 times.
✓ Branch 7 taken 41 times.
246 if (xattrs_.find(merge_list[i]) == xattrs_.end()) {
147
1/2
✓ Branch 2 taken 205 times.
✗ Branch 3 not taken.
205 result += merge_list[i];
148
1/2
✓ Branch 1 taken 205 times.
✗ Branch 2 not taken.
205 result.push_back('\0');
149 }
150 }
151 82 }
152 328 for (map<string, string>::const_iterator i = xattrs_.begin(),
153 164 iEnd = xattrs_.end();
154
2/2
✓ Branch 1 taken 246 times.
✓ Branch 2 taken 164 times.
410 i != iEnd;
155 246 ++i) {
156
1/2
✓ Branch 2 taken 246 times.
✗ Branch 3 not taken.
246 result += i->first;
157
1/2
✓ Branch 1 taken 246 times.
✗ Branch 2 not taken.
246 result.push_back('\0');
158 }
159 164 return result;
160 }
161
162
163 23534 bool XattrList::Set(const string &key, const string &value) {
164
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23534 times.
23534 if (key.empty())
165 return false;
166
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 23493 times.
23534 if (key.length() > 256)
167 41 return false;
168
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 23452 times.
23493 if (key.find('\0') != string::npos)
169 41 return false;
170
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 23411 times.
23452 if (value.length() > 256)
171 41 return false;
172
173
1/2
✓ Branch 1 taken 23411 times.
✗ Branch 2 not taken.
23411 const map<string, string>::iterator iter = xattrs_.find(key);
174
2/2
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 23370 times.
23411 if (iter != xattrs_.end()) {
175
1/2
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 iter->second = value;
176 } else {
177
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 23329 times.
23370 if (xattrs_.size() >= 256)
178 41 return false;
179
2/4
✓ Branch 1 taken 23329 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 23329 times.
✗ Branch 5 not taken.
23329 xattrs_[key] = value;
180 }
181 23370 return true;
182 }
183
184
185 205 bool XattrList::Remove(const string &key) {
186
1/2
✓ Branch 1 taken 205 times.
✗ Branch 2 not taken.
205 const map<string, string>::iterator iter = xattrs_.find(key);
187
2/2
✓ Branch 2 taken 164 times.
✓ Branch 3 taken 41 times.
205 if (iter != xattrs_.end()) {
188
1/2
✓ Branch 1 taken 164 times.
✗ Branch 2 not taken.
164 xattrs_.erase(iter);
189 164 return true;
190 }
191 41 return false;
192 }
193
194
195 /**
196 * If the list of attributes is empty, Serialize returns NULL. Deserialize
197 * can deal with NULL pointers.
198 */
199 205 void XattrList::Serialize(unsigned char **outbuf,
200 unsigned *size,
201 const std::vector<std::string> *blacklist) const {
202
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 164 times.
205 if (xattrs_.empty()) {
203 41 *size = 0;
204 41 *outbuf = NULL;
205 82 return;
206 }
207
208 164 XattrHeader header(xattrs_.size());
209 164 uint32_t packed_size = sizeof(header);
210
211 // Determine size of the buffer (allocate space for max num of attributes)
212 XattrEntry *entries = reinterpret_cast<XattrEntry *>(
213 164 smalloc(header.num_xattrs * sizeof(XattrEntry)));
214 164 unsigned ientries = 0;
215 328 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
216 164 it_att_end = xattrs_.end();
217
2/2
✓ Branch 1 taken 492 times.
✓ Branch 2 taken 164 times.
656 it_att != it_att_end;
218 492 ++it_att) {
219 // Only serialize non-blacklist items
220
2/2
✓ Branch 0 taken 246 times.
✓ Branch 1 taken 246 times.
492 if (blacklist != NULL) {
221 246 bool skip = false;
222
2/2
✓ Branch 1 taken 451 times.
✓ Branch 2 taken 41 times.
492 for (unsigned i_bl = 0; i_bl < blacklist->size(); ++i_bl) {
223
3/4
✓ Branch 3 taken 451 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 205 times.
✓ Branch 6 taken 246 times.
451 if (HasPrefix(it_att->first, (*blacklist)[i_bl],
224 true /* ignore_case */)) {
225 205 skip = true;
226 205 break;
227 }
228 }
229
2/2
✓ Branch 0 taken 205 times.
✓ Branch 1 taken 41 times.
246 if (skip)
230 205 continue;
231 }
232 /*entries[ientries] =*/
233 287 new (entries + ientries) XattrEntry(it_att->first, it_att->second);
234 287 packed_size += entries[ientries].GetSize();
235 287 ientries++;
236 }
237
238 // We might have skipped all attributes
239
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 123 times.
164 if (ientries == 0) {
240 41 free(entries);
241 41 *size = 0;
242 41 *outbuf = NULL;
243 41 return;
244 }
245
246 // Copy data into buffer
247 123 header.num_xattrs = ientries;
248 123 *size = packed_size;
249 123 *outbuf = reinterpret_cast<unsigned char *>(smalloc(packed_size));
250 123 memcpy(*outbuf, &header, sizeof(header));
251 123 unsigned pos = sizeof(header);
252
2/2
✓ Branch 0 taken 287 times.
✓ Branch 1 taken 123 times.
410 for (unsigned i = 0; i < header.num_xattrs; ++i) {
253 287 memcpy(*outbuf + pos, &entries[i], entries[i].GetSize());
254 287 pos += entries[i].GetSize();
255 }
256
257 123 free(entries);
258 }
259
260
261 //------------------------------------------------------------------------------
262
263
264 287 string XattrList::XattrEntry::GetKey() const {
265
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 287 times.
287 if (len_key == 0)
266 return "";
267
1/2
✓ Branch 2 taken 287 times.
✗ Branch 3 not taken.
287 return string(data, len_key);
268 }
269
270
271 2214 uint16_t XattrList::XattrEntry::GetSize() const {
272 2214 return sizeof(len_key) + sizeof(len_value) + uint16_t(len_key)
273 2214 + uint16_t(len_value);
274 }
275
276
277 287 string XattrList::XattrEntry::GetValue() const {
278
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 205 times.
287 if (len_value == 0)
279
1/2
✓ Branch 2 taken 82 times.
✗ Branch 3 not taken.
82 return "";
280
1/2
✓ Branch 2 taken 205 times.
✗ Branch 3 not taken.
205 return string(&data[len_key], len_value);
281 }
282
283
284 287 XattrList::XattrEntry::XattrEntry(const string &key, const string &value)
285 287 : len_key(key.size()), len_value(value.size()) {
286 287 memcpy(data, key.data(), len_key);
287 287 memcpy(data + len_key, value.data(), len_value);
288 287 }
289