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