GCC Code Coverage Report


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