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 |