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