GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/xattr.cc
Date: 2025-07-27 02:42:09
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 4 XattrList *XattrList::CreateFromFile(const std::string &path) {
33 // Parse the \0 separated list of extended attribute keys
34 char *list;
35 4 ssize_t sz_list = platform_llistxattr(path.c_str(), NULL, 0);
36
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
4 if ((sz_list < 0) || (sz_list > 64 * 1024)) {
37 1 return NULL;
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (sz_list == 0) {
39 // No extended attributes
40 return new XattrList();
41 }
42 3 list = reinterpret_cast<char *>(alloca(sz_list));
43 3 sz_list = platform_llistxattr(path.c_str(), list, sz_list);
44
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sz_list < 0) {
45 return NULL;
46
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } 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 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
6 vector<string> keys = SplitString(string(list, sz_list), '\0');
52
53 // Retrieve extended attribute values
54
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 XattrList *result = new XattrList();
55 char value[256];
56
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 3 times.
14 for (unsigned i = 0; i < keys.size(); ++i) {
57
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 8 times.
11 if (keys[i].empty())
58 3 continue;
59 8 const ssize_t sz_value = platform_lgetxattr(path.c_str(), keys[i].c_str(),
60 value, 256);
61
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (sz_value < 0)
62 1 continue;
63
2/4
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
7 result->Set(keys[i], string(value, sz_value));
64 }
65 3 return result;
66 3 }
67
68
69 10 XattrList *XattrList::Deserialize(const unsigned char *inbuf,
70 const unsigned size) {
71
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (inbuf == NULL)
72
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 return new XattrList();
73
74
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
9 UniquePtr<XattrList> result(new XattrList());
75
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (size < sizeof(XattrHeader))
76 1 return NULL;
77 8 XattrHeader header;
78 8 memcpy(&header, inbuf, sizeof(header));
79
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (header.version != kVersion)
80 1 return NULL;
81 7 unsigned pos = sizeof(header);
82
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 3 times.
14 for (unsigned i = 0; i < header.num_xattrs; ++i) {
83 11 XattrEntry entry;
84 11 unsigned size_preamble = // NOLINT
85 sizeof(entry.len_key) + sizeof(entry.len_value);
86
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (size - pos < size_preamble)
87 4 return NULL;
88 10 memcpy(&entry, inbuf + pos, size_preamble);
89
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9 times.
10 if (size - pos < entry.GetSize())
90 1 return NULL;
91
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 7 times.
9 if (entry.GetSize() == size_preamble)
92 2 return NULL;
93 7 pos += size_preamble;
94 7 memcpy(entry.data, inbuf + pos, entry.GetSize() - size_preamble);
95 7 pos += entry.GetSize() - size_preamble;
96
3/6
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
7 const bool retval = result->Set(entry.GetKey(), entry.GetValue());
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!retval)
98 return NULL;
99 }
100 3 return result.Release();
101 9 }
102
103
104 12 bool XattrList::Has(const string &key) const {
105
1/2
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 return xattrs_.find(key) != xattrs_.end();
106 }
107
108
109 271 bool XattrList::Get(const string &key, string *value) const {
110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 271 times.
271 assert(value);
111
1/2
✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
271 const map<string, string>::const_iterator iter = xattrs_.find(key);
112
2/2
✓ Branch 2 taken 269 times.
✓ Branch 3 taken 2 times.
271 if (iter != xattrs_.end()) {
113
1/2
✓ Branch 2 taken 269 times.
✗ Branch 3 not taken.
269 *value = iter->second;
114 269 return true;
115 }
116 2 return false;
117 }
118
119
120 13 vector<string> XattrList::ListKeys() const {
121 13 vector<string> result;
122 26 for (map<string, string>::const_iterator i = xattrs_.begin(),
123 13 iEnd = xattrs_.end();
124
2/2
✓ Branch 1 taken 282 times.
✓ Branch 2 taken 13 times.
295 i != iEnd;
125 282 ++i) {
126
1/2
✓ Branch 2 taken 282 times.
✗ Branch 3 not taken.
282 result.push_back(i->first);
127 }
128 13 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 4 string XattrList::ListKeysPosix(const string &merge_with) const {
140 4 string result;
141
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if (!merge_with.empty()) {
142
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 vector<string> merge_list = SplitString(merge_with, '\0');
143
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
10 for (unsigned i = 0; i < merge_list.size(); ++i) {
144
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6 times.
8 if (merge_list[i].empty())
145 2 continue;
146
3/5
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 1 times.
6 if (xattrs_.find(merge_list[i]) == xattrs_.end()) {
147
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 result += merge_list[i];
148
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 result.push_back('\0');
149 }
150 }
151 2 }
152 8 for (map<string, string>::const_iterator i = xattrs_.begin(),
153 4 iEnd = xattrs_.end();
154
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 4 times.
10 i != iEnd;
155 6 ++i) {
156
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 result += i->first;
157
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 result.push_back('\0');
158 }
159 4 return result;
160 }
161
162
163 574 bool XattrList::Set(const string &key, const string &value) {
164
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 574 times.
574 if (key.empty())
165 return false;
166
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 573 times.
574 if (key.length() > 256)
167 1 return false;
168
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 572 times.
573 if (key.find('\0') != string::npos)
169 1 return false;
170
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 571 times.
572 if (value.length() > 256)
171 1 return false;
172
173
1/2
✓ Branch 1 taken 571 times.
✗ Branch 2 not taken.
571 const map<string, string>::iterator iter = xattrs_.find(key);
174
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 570 times.
571 if (iter != xattrs_.end()) {
175
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 iter->second = value;
176 } else {
177
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 569 times.
570 if (xattrs_.size() >= 256)
178 1 return false;
179
2/4
✓ Branch 1 taken 569 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 569 times.
✗ Branch 5 not taken.
569 xattrs_[key] = value;
180 }
181 570 return true;
182 }
183
184
185 5 bool XattrList::Remove(const string &key) {
186
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 const map<string, string>::iterator iter = xattrs_.find(key);
187
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
5 if (iter != xattrs_.end()) {
188
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 xattrs_.erase(iter);
189 4 return true;
190 }
191 1 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 5 void XattrList::Serialize(unsigned char **outbuf,
200 unsigned *size,
201 const std::vector<std::string> *blacklist) const {
202
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
5 if (xattrs_.empty()) {
203 1 *size = 0;
204 1 *outbuf = NULL;
205 2 return;
206 }
207
208 4 XattrHeader header(xattrs_.size());
209 4 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 4 smalloc(header.num_xattrs * sizeof(XattrEntry)));
214 4 unsigned ientries = 0;
215 8 for (map<string, string>::const_iterator it_att = xattrs_.begin(),
216 4 it_att_end = xattrs_.end();
217
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 4 times.
16 it_att != it_att_end;
218 12 ++it_att) {
219 // Only serialize non-blacklist items
220
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 if (blacklist != NULL) {
221 6 bool skip = false;
222
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 1 times.
12 for (unsigned i_bl = 0; i_bl < blacklist->size(); ++i_bl) {
223
3/4
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 6 times.
11 if (HasPrefix(it_att->first, (*blacklist)[i_bl],
224 true /* ignore_case */)) {
225 5 skip = true;
226 5 break;
227 }
228 }
229
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (skip)
230 5 continue;
231 }
232 /*entries[ientries] =*/
233 7 new (entries + ientries) XattrEntry(it_att->first, it_att->second);
234 7 packed_size += entries[ientries].GetSize();
235 7 ientries++;
236 }
237
238 // We might have skipped all attributes
239
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (ientries == 0) {
240 1 free(entries);
241 1 *size = 0;
242 1 *outbuf = NULL;
243 1 return;
244 }
245
246 // Copy data into buffer
247 3 header.num_xattrs = ientries;
248 3 *size = packed_size;
249 3 *outbuf = reinterpret_cast<unsigned char *>(smalloc(packed_size));
250 3 memcpy(*outbuf, &header, sizeof(header));
251 3 unsigned pos = sizeof(header);
252
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 for (unsigned i = 0; i < header.num_xattrs; ++i) {
253 7 memcpy(*outbuf + pos, &entries[i], entries[i].GetSize());
254 7 pos += entries[i].GetSize();
255 }
256
257 3 free(entries);
258 }
259
260
261 //------------------------------------------------------------------------------
262
263
264 7 string XattrList::XattrEntry::GetKey() const {
265
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (len_key == 0)
266 return "";
267
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 return string(data, len_key);
268 }
269
270
271 54 uint16_t XattrList::XattrEntry::GetSize() const {
272 54 return sizeof(len_key) + sizeof(len_value) + uint16_t(len_key)
273 54 + uint16_t(len_value);
274 }
275
276
277 7 string XattrList::XattrEntry::GetValue() const {
278
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (len_value == 0)
279
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 return "";
280
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return string(&data[len_key], len_value);
281 }
282
283
284 7 XattrList::XattrEntry::XattrEntry(const string &key, const string &value)
285 7 : len_key(key.size()), len_value(value.size()) {
286 7 memcpy(data, key.data(), len_key);
287 7 memcpy(data + len_key, value.data(), len_value);
288 7 }
289