GCC Code Coverage Report


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