Project
Loading...
Searching...
No Matches
Container.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include "AODNative/Container.h"
13#include "AODNative/Codecs.h"
14#include "AODNative/Errors.h"
15#include "Cursor.h"
16#include <algorithm>
17#include <regex>
18#include <unordered_set>
19
20namespace o2::aod::native
21{
22
23namespace
24{
25// Big enough for the file header and for a TDirectory record (including its UUID).
26constexpr size_t kHeaderReadSize = 128;
27constexpr size_t kDirectoryReadSize = 64;
28// Largest gap between the TTrees of a DF still read together with them.
29constexpr uint64_t kMetadataGap = 4 * 1024;
30// Fixed part of a TBasket key, after which come the class name, name and title.
31constexpr size_t kBasketHeaderTail = 19;
32// Smallest possible key header: 32-bit seeks and three empty strings.
33constexpr size_t kMinKeySize = 29;
34// Metadata objects (trees, maps) in AO2Ds are a few MB at most. A larger
35// object length can only come from a corrupted key, so refuse to allocate it.
36constexpr uint32_t kMaxMetadataSize = 256 * 1024 * 1024;
37
38std::vector<uint8_t> readRange(ByteSource& source, uint64_t off, size_t len)
39{
40 // Check before allocating: the length comes from the file.
41 if (off > source.size() || len > source.size() - off) {
42 throw CorruptData(source.url(), off, "range of " + std::to_string(len) + " bytes past the end of the file");
43 }
44 std::vector<uint8_t> buf(len);
45 source.readInto(off, len, buf.data());
46 return buf;
47}
48} // namespace
49
50RootFile::RootFile(std::shared_ptr<ByteSource> source)
51 : mSource(std::move(source)),
52 mUrl(mSource->url())
53{
54 auto buf = readRange(*mSource, 0, std::min<uint64_t>(kHeaderReadSize, mSource->size()));
55 Cursor c{buf, mUrl};
56 auto magic = c.bytes(4);
57 if (std::memcmp(magic.data(), "root", 4) != 0) {
58 throw FormatError(mUrl + " is not a ROOT file");
59 }
60 auto& h = mHeader;
61 h.version = c.i32();
62 h.begin = c.i32();
63 bool big = h.version >= 1000000;
64 h.end = big ? c.u64() : c.u32();
65 h.seekFree = big ? c.u64() : c.u32();
66 h.nbytesFree = c.i32();
67 h.nfree = c.i32();
68 h.nbytesName = c.i32();
69 h.units = c.u8();
70 h.compress = c.i32();
71 h.seekInfo = big ? c.u64() : c.u32();
72 h.nbytesInfo = c.i32();
73 if (h.begin <= 0 || h.nbytesName <= 0) {
74 c.fail("invalid file header");
75 }
76}
77
78KeyInfo RootFile::parseKey(std::span<const uint8_t> buf, uint64_t fileOffset)
79{
80 Cursor c{buf, mUrl, fileOffset};
81 KeyInfo k;
82 k.nbytes = c.u32();
83 k.version = c.i16();
84 k.objlen = c.u32();
85 c.u32(); // fDatime
86 k.keylen = c.u16();
87 k.cycle = c.u16();
88 bool big = k.version > 1000;
89 k.seekKey = big ? c.u64() : c.u32();
90 k.seekPdir = big ? c.u64() : c.u32();
91 k.className = c.string();
92 k.name = c.string();
93 k.title = c.string();
94 if (k.keylen > k.nbytes || c.pos() > k.keylen) {
95 c.fail("inconsistent key header for " + k.name);
96 }
97 return k;
98}
99
100DirectoryInfo RootFile::readDirectory(uint64_t offset)
101{
102 if (offset >= mSource->size()) {
103 throw CorruptData(mUrl, offset, "directory record past the end of the file");
104 }
105 auto buf = readRange(*mSource, offset, std::min<uint64_t>(kDirectoryReadSize, mSource->size() - offset));
106 Cursor c{buf, mUrl, offset};
107 DirectoryInfo d;
108 d.version = c.i16();
109 c.u32(); // fDatimeC
110 c.u32(); // fDatimeM
111 d.nbytesKeys = c.i32();
112 d.nbytesName = c.i32();
113 bool big = d.version > 1000;
114 d.seekDir = big ? c.u64() : c.u32();
115 d.seekParent = big ? c.u64() : c.u32();
116 d.seekKeys = big ? c.u64() : c.u32();
117 return d;
118}
119
120std::vector<KeyInfo> RootFile::readKeyList(DirectoryInfo const& dir)
121{
122 if (dir.seekKeys == 0 || dir.nbytesKeys <= 0) {
123 return {};
124 }
125 auto buf = readRange(*mSource, dir.seekKeys, dir.nbytesKeys);
126 auto header = parseKey(buf, dir.seekKeys);
127 Cursor c{buf, mUrl, dir.seekKeys};
128 c.seek(header.keylen);
129 auto nkeys = c.i32();
130 if (nkeys < 0 || (size_t)nkeys > c.remaining() / kMinKeySize) {
131 c.fail("invalid number of keys " + std::to_string(nkeys));
132 }
133 std::vector<KeyInfo> keys;
134 keys.reserve(nkeys);
135 for (int i = 0; i < nkeys; ++i) {
136 auto start = c.pos();
137 // The key length is only known after parsing, so parse from here to the end
138 // of the list and advance by the key length.
139 keys.push_back(parseKey(c.buffer().subspan(start), dir.seekKeys + start));
140 c.seek(start + keys.back().keylen);
141 }
142 return keys;
143}
144
145const std::vector<KeyInfo>& RootFile::topKeys()
146{
147 if (!mTopKeys) {
148 auto dir = readDirectory((uint64_t)mHeader.begin + mHeader.nbytesName);
149 mTopKeys = readKeyList(dir);
150 }
151 return *mTopKeys;
152}
153
154std::vector<KeyInfo> highestCycles(std::vector<KeyInfo> const& keys)
155{
156 std::vector<KeyInfo> result;
157 for (auto& k : keys) {
158 auto it = std::find_if(result.begin(), result.end(), [&k](KeyInfo const& r) { return r.name == k.name; });
159 if (it == result.end()) {
160 result.push_back(k);
161 } else if (k.cycle > it->cycle) {
162 *it = k;
163 }
164 }
165 return result;
166}
167
168std::optional<KeyInfo> RootFile::findTopKey(std::string const& name)
169{
170 std::optional<KeyInfo> result;
171 for (auto& k : topKeys()) {
172 if (k.name == name && (!result || k.cycle > result->cycle)) {
173 result = k;
174 }
175 }
176 return result;
177}
178
179std::vector<std::string> RootFile::dataFrames()
180{
181 static const std::regex tfRegex("/?DF_([0-9]+)(|-.*)$");
182 std::vector<uint64_t> numbers;
183 std::unordered_set<uint64_t> seen;
184 for (auto& k : topKeys()) {
185 std::smatch m;
186 if (std::regex_match(k.name, m, tfRegex)) {
187 auto n = std::stoull(m[1].str());
188 if (seen.insert(n).second) {
189 numbers.push_back(n);
190 }
191 }
192 }
193 std::sort(numbers.begin(), numbers.end());
194 auto parents = readStringMap("parentFiles");
195 if (!parents.empty()) {
196 // Process DFs grouped by parent file, to avoid swapping between files.
197 // DataInputDirector uses an unstable sort here; ties are kept in numeric order.
198 auto parentOf = [&parents](uint64_t n) -> std::string const& {
199 static const std::string none;
200 auto it = parents.find("DF_" + std::to_string(n));
201 return it == parents.end() ? none : it->second;
202 };
203 std::stable_sort(numbers.begin(), numbers.end(), [&](uint64_t a, uint64_t b) { return parentOf(a) < parentOf(b); });
204 }
205 std::vector<std::string> result;
206 result.reserve(numbers.size());
207 for (auto n : numbers) {
208 result.push_back("DF_" + std::to_string(n));
209 }
210 return result;
211}
212
213std::vector<KeyInfo> RootFile::keysOf(const std::string& df)
214{
215 if (auto it = mKeysOf.find(df); it != mKeysOf.end()) {
216 return it->second;
217 }
218 auto key = findTopKey(df);
219 if (!key) {
220 for (auto& k : topKeys()) {
221 if (k.name.starts_with(df + "-")) {
222 throw UnsupportedLayout("flat " + df + "-<table> layout (" + k.className + ") in " + mUrl);
223 }
224 }
225 throw FormatError("no directory " + df + " in " + mUrl);
226 }
227 if (key->className != "TDirectoryFile" && key->className != "TDirectory") {
228 throw UnsupportedLayout(df + " in " + mUrl + " is a " + key->className + ", not a TDirectoryFile");
229 }
230 if (key->compressed()) {
231 throw UnsupportedLayout("compressed directory record for " + df + " in " + mUrl);
232 }
233 auto dir = readDirectory(key->seekKey + key->keylen);
234 auto keys = highestCycles(readKeyList(dir));
235
236 // The TTrees of a DF are written next to each other: ranges less than
237 // kMetadataGap apart are read as one.
238 struct Wanted {
239 uint64_t offset, length, seekKey;
240 };
241 std::vector<Wanted> wanted;
242 for (auto const& k : keys) {
243 auto stored = (uint64_t)k.nbytes - k.keylen;
244 auto offset = k.seekKey + k.keylen;
245 bool listed = mTreesToPrefetch.empty() || std::ranges::find(mTreesToPrefetch, k.name) != mTreesToPrefetch.end();
246 // Anything odd is left to readPayload(), which reports it.
247 if (mSource->canMap() || !listed || k.className != "TTree" || k.nbytes < k.keylen || stored > kMaxMetadataSize ||
248 offset > mSource->size() || stored > mSource->size() - offset) {
249 continue;
250 }
251 wanted.push_back({offset, stored, k.seekKey});
252 }
253 std::ranges::sort(wanted, {}, &Wanted::offset);
254 std::vector<std::pair<uint64_t, std::vector<uint8_t>>> spans;
255 for (auto const& w : wanted) {
256 if (!spans.empty()) {
257 auto& [start, bytes] = spans.back();
258 auto end = start + bytes.size();
259 if (w.offset <= end + kMetadataGap && w.offset + w.length - start <= kMaxMetadataSize) {
260 bytes.resize(std::max(end, w.offset + w.length) - start);
261 continue;
262 }
263 }
264 spans.emplace_back(w.offset, std::vector<uint8_t>(w.length));
265 }
266 std::vector<ReadRequest> requests;
267 for (auto& [start, bytes] : spans) {
268 requests.push_back({start, bytes.size(), bytes.data()});
269 }
270 mPrefetched.clear();
271 if (!requests.empty()) {
272 mSource->readvInto(requests);
273 }
274 size_t span = 0;
275 for (auto const& w : wanted) {
276 while (spans[span].first + spans[span].second.size() < w.offset + w.length) {
277 ++span;
278 }
279 auto const* at = spans[span].second.data() + (w.offset - spans[span].first);
280 mPrefetched[w.seekKey].assign(at, at + w.length);
281 }
282 mKeysOf.emplace(df, keys);
283 return keys;
284}
285
286std::vector<uint8_t> RootFile::readStored(const KeyInfo& key)
287{
288 auto stored = key.nbytes - key.keylen;
289 if (auto it = mPrefetched.find(key.seekKey); it != mPrefetched.end() && it->second.size() == stored) {
290 auto bytes = std::move(it->second);
291 mPrefetched.erase(it);
292 return bytes;
293 }
294 return readRange(*mSource, key.seekKey + key.keylen, stored);
295}
296
297std::vector<uint8_t> RootFile::readPayload(const KeyInfo& key)
298{
299 auto offset = key.seekKey + key.keylen;
300 auto stored = key.nbytes - key.keylen;
301 if (key.objlen > kMaxMetadataSize || stored > kMaxMetadataSize) {
302 throw CorruptData(mUrl, key.seekKey, "object " + key.name + " of " + std::to_string(key.objlen) + " bytes is too large for metadata");
303 }
304 if (!key.compressed()) {
305 return readStored(key);
306 }
307 auto compressed = readStored(key);
308 std::vector<uint8_t> result(key.objlen);
309 decompressBounded(compressed, key.objlen, result.data(), result.size(), mUrl, offset);
310 return result;
311}
312
314{
315 if (key.className != "TTree") {
316 throw UnsupportedLayout(key.name + " in " + mUrl + " is a " + key.className + ", not a TTree");
317 }
318 auto payload = readPayload(key);
319 auto tree = readTreePayload(payload, key.keylen, mUrl, key.seekKey + key.keylen);
320 // When fBasketEntry has no slot past the last normal basket, its entry count
321 // is only in the basket header (fNevBuf).
322 for (auto& branch : tree.branches) {
323 for (auto& basket : branch.baskets) {
324 if (basket.nEntries >= 0) {
325 continue;
326 }
327 auto buf = readRange(*mSource, basket.seek, std::min<uint64_t>(basket.nbytes, kHeaderReadSize * 4));
328 auto k = parseKey(buf, basket.seek);
329 Cursor c{buf, mUrl, basket.seek};
330 c.seek(k.keylen - kBasketHeaderTail);
331 c.u16(); // fVersion
332 c.i32(); // fBufferSize
333 c.i32(); // fNevBufSize
334 basket.nEntries = c.i32();
335 }
336 }
337 return tree;
338}
339
340std::map<std::string, std::string> RootFile::readStringMap(const std::string& name)
341{
342 if (auto it = mStringMaps.find(name); it != mStringMaps.end()) {
343 return it->second;
344 }
345 auto key = findTopKey(name);
346 if (!key) {
347 return mStringMaps[name];
348 }
349 if (key->className != "TMap") {
350 throw UnsupportedLayout(name + " in " + mUrl + " is a " + key->className + ", not a TMap");
351 }
352 auto payload = readPayload(*key);
353 return mStringMaps[name] = readStringMapPayload(payload, key->keylen, mUrl, key->seekKey + key->keylen);
354}
355
356} // namespace o2::aod::native
std::string url
std::vector< std::string > keys
int32_t i
uint32_t c
Definition RawData.h:2
size_t bytes
StringRef key
Class for time synchronization of RawReader instances.
RootFile(std::shared_ptr< ByteSource >)
Definition Container.cxx:50
const std::vector< KeyInfo > & topKeys()
All the keys of the top directory, every cycle, in on-disk order.
TreeInfo readTree(const KeyInfo &)
std::vector< std::string > dataFrames()
std::map< std::string, std::string > readStringMap(const std::string &key)
Empty map if the key does not exist. Read once per key.
FileHeader const & header() const
Definition Container.h:60
std::vector< uint8_t > readPayload(const KeyInfo &)
The uncompressed payload of a key.
std::vector< KeyInfo > keysOf(const std::string &df)
GLdouble n
Definition glcorearb.h:1982
const GLfloat * m
Definition glcorearb.h:4066
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint GLuint end
Definition glcorearb.h:469
GLuint const GLchar * name
Definition glcorearb.h:781
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLintptr offset
Definition glcorearb.h:660
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
GLboolean r
Definition glcorearb.h:1233
GLuint start
Definition glcorearb.h:469
GLenum GLenum GLsizei len
Definition glcorearb.h:4232
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition glcorearb.h:2514
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
TreeInfo readTreePayload(std::span< const uint8_t > payload, uint16_t keylen, std::string const &file, uint64_t fileOffset)
Definition Objects.cxx:544
size_t decompressBounded(std::span< const uint8_t > src, uint32_t objLen, uint8_t *dst, size_t maxOut, std::string const &file="<memory>", uint64_t fileOffset=0)
Definition Codecs.cxx:310
std::vector< KeyInfo > highestCycles(std::vector< KeyInfo > const &keys)
Keeps only the highest cycle of each name, preserving first-seen order.
std::map< std::string, std::string > readStringMapPayload(std::span< const uint8_t > payload, uint16_t keylen, std::string const &file, uint64_t fileOffset)
Deserializes a TMap of TObjString to TObjString (metaData, parentFiles).
Definition Objects.cxx:550
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
const std::string str