Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RMiniFile.cxx
Go to the documentation of this file.
1/// \file RMiniFile.cxx
2/// \ingroup NTuple ROOT7
3/// \author Jakob Blomer <jblomer@cern.ch>
4/// \date 2019-12-22
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
16#include "Rtypes.h"
17#include <ROOT/RConfig.hxx>
18#include <ROOT/RError.hxx>
19#include <ROOT/RMiniFile.hxx>
20#include <ROOT/RRawFile.hxx>
21#include <ROOT/RNTupleZip.hxx>
24
25#include <Byteswap.h>
26#include <TBufferFile.h>
27#include <TDirectory.h>
28#include <TError.h>
29#include <TFile.h>
30#include <TKey.h>
31#include <TObjString.h>
32#include <TUUID.h>
34
35#include <xxhash.h>
36
37#include <algorithm>
38#include <cassert>
39#include <cerrno>
40#include <cstdio>
41#include <cstring>
42#include <memory>
43#include <string>
44#include <chrono>
45
46#ifdef R__LINUX
47#include <fcntl.h>
48#endif
49
50#ifndef R__LITTLE_ENDIAN
51#ifdef R__BYTESWAP
52// `R__BYTESWAP` is defined in RConfig.hxx for little-endian architectures; undefined otherwise
53#define R__LITTLE_ENDIAN 1
54#else
55#define R__LITTLE_ENDIAN 0
56#endif
57#endif /* R__LITTLE_ENDIAN */
58
59namespace {
60
61// The following types are used to read and write the TFile binary format
62
63/// Big-endian 16-bit unsigned integer
64class RUInt16BE {
65private:
66 std::uint16_t fValBE = 0;
67 static std::uint16_t Swap(std::uint16_t val)
68 {
69#if R__LITTLE_ENDIAN == 1
70 return RByteSwap<sizeof(val)>::bswap(val);
71#else
72 return val;
73#endif
74 }
75
76public:
77 RUInt16BE() = default;
78 explicit RUInt16BE(const std::uint16_t val) : fValBE(Swap(val)) {}
79 operator std::uint16_t() const { return Swap(fValBE); }
80 RUInt16BE &operator=(const std::uint16_t val)
81 {
82 fValBE = Swap(val);
83 return *this;
84 }
85};
86
87/// Big-endian 32-bit unsigned integer
88class RUInt32BE {
89private:
90 std::uint32_t fValBE = 0;
91 static std::uint32_t Swap(std::uint32_t val)
92 {
93#if R__LITTLE_ENDIAN == 1
94 return RByteSwap<sizeof(val)>::bswap(val);
95#else
96 return val;
97#endif
98 }
99
100public:
101 RUInt32BE() = default;
102 explicit RUInt32BE(const std::uint32_t val) : fValBE(Swap(val)) {}
103 operator std::uint32_t() const { return Swap(fValBE); }
104 RUInt32BE &operator=(const std::uint32_t val)
105 {
106 fValBE = Swap(val);
107 return *this;
108 }
109};
110
111/// Big-endian 32-bit signed integer
112class RInt32BE {
113private:
114 std::int32_t fValBE = 0;
115 static std::int32_t Swap(std::int32_t val)
116 {
117#if R__LITTLE_ENDIAN == 1
118 return RByteSwap<sizeof(val)>::bswap(val);
119#else
120 return val;
121#endif
122 }
123
124public:
125 RInt32BE() = default;
126 explicit RInt32BE(const std::int32_t val) : fValBE(Swap(val)) {}
127 operator std::int32_t() const { return Swap(fValBE); }
128 RInt32BE &operator=(const std::int32_t val)
129 {
130 fValBE = Swap(val);
131 return *this;
132 }
133};
134
135/// Big-endian 64-bit unsigned integer
136class RUInt64BE {
137private:
138 std::uint64_t fValBE = 0;
139 static std::uint64_t Swap(std::uint64_t val)
140 {
141#if R__LITTLE_ENDIAN == 1
142 return RByteSwap<sizeof(val)>::bswap(val);
143#else
144 return val;
145#endif
146 }
147
148public:
149 RUInt64BE() = default;
150 explicit RUInt64BE(const std::uint64_t val) : fValBE(Swap(val)) {}
151 operator std::uint64_t() const { return Swap(fValBE); }
152 RUInt64BE &operator=(const std::uint64_t val)
153 {
154 fValBE = Swap(val);
155 return *this;
156 }
157};
158
159#pragma pack(push, 1)
160/// A name (type, identifies, ...) in the TFile binary format
161struct RTFString {
162 unsigned char fLName{0};
163 char fData[255];
164 RTFString() = default;
165 RTFString(const std::string &str)
166 {
167 // The length of strings with 255 characters and longer are encoded with a 32-bit integer following the first
168 // byte. This is currently not handled.
169 R__ASSERT(str.length() < 255);
170 fLName = str.length();
171 memcpy(fData, str.data(), fLName);
172 }
173 std::size_t GetSize() const
174 {
175 // A length of 255 is special and means that the first byte is followed by a 32-bit integer with the actual
176 // length.
177 R__ASSERT(fLName != 255);
178 return 1 + fLName;
179 }
180};
181
182/// The timestamp format used in TFile; the default constructor initializes with the current time
183struct RTFDatetime {
184 RUInt32BE fDatetime;
185 RTFDatetime()
186 {
187 auto now = std::chrono::system_clock::now();
188 auto tt = std::chrono::system_clock::to_time_t(now);
189 auto tm = *localtime(&tt);
190 fDatetime = (tm.tm_year + 1900 - 1995) << 26 | (tm.tm_mon + 1) << 22 | tm.tm_mday << 17 | tm.tm_hour << 12 |
191 tm.tm_min << 6 | tm.tm_sec;
192 }
193 explicit RTFDatetime(RUInt32BE val) : fDatetime(val) {}
194};
195
196/// The key part of a TFile record excluding the class, object, and title names
197struct RTFKey {
198 static constexpr unsigned kBigKeyVersion = 1000;
199
200 RInt32BE fNbytes{0};
201 RUInt16BE fVersion{4};
202 RUInt32BE fObjLen{0};
203 RTFDatetime fDatetime;
204 RUInt16BE fKeyLen{0};
205 RUInt16BE fCycle{1};
206 union {
207 struct {
208 RUInt32BE fSeekKey{0};
209 RUInt32BE fSeekPdir{0};
210 } fInfoShort;
211 struct {
212 RUInt64BE fSeekKey{0};
213 RUInt64BE fSeekPdir{0};
214 } fInfoLong;
215 };
216
217 RTFKey() : fInfoLong() {}
218 RTFKey(std::uint64_t seekKey, std::uint64_t seekPdir, const RTFString &clName, const RTFString &objName,
219 const RTFString &titleName, std::size_t szObjInMem, std::size_t szObjOnDisk = 0)
220 {
221 R__ASSERT(szObjInMem <= std::numeric_limits<std::uint32_t>::max());
222 R__ASSERT(szObjOnDisk <= std::numeric_limits<std::uint32_t>::max());
223 // For writing, we alywas produce "big" keys with 64-bit SeekKey and SeekPdir.
224 fVersion = fVersion + kBigKeyVersion;
225 fObjLen = szObjInMem;
226 fKeyLen = GetHeaderSize() + clName.GetSize() + objName.GetSize() + titleName.GetSize();
227 fInfoLong.fSeekKey = seekKey;
228 fInfoLong.fSeekPdir = seekPdir;
229 // Depends on fKeyLen being set
230 fNbytes = fKeyLen + ((szObjOnDisk == 0) ? szObjInMem : szObjOnDisk);
231 }
232
233 std::uint32_t GetSize() const
234 {
235 // Negative size indicates a gap in the file
236 if (fNbytes < 0)
237 return -fNbytes;
238 return fNbytes;
239 }
240
241 std::uint32_t GetHeaderSize() const
242 {
243 if (fVersion >= kBigKeyVersion)
244 return 18 + sizeof(fInfoLong);
245 return 18 + sizeof(fInfoShort);
246 }
247
248 std::uint64_t GetSeekKey() const
249 {
250 if (fVersion >= kBigKeyVersion)
251 return fInfoLong.fSeekKey;
252 return fInfoShort.fSeekKey;
253 }
254};
255
256/// The TFile global header
257struct RTFHeader {
258 static constexpr unsigned kBEGIN = 100;
259 static constexpr unsigned kBigHeaderVersion = 1000000;
260
261 char fMagic[4]{'r', 'o', 'o', 't'};
262 RUInt32BE fVersion{(ROOT_VERSION_CODE >> 16) * 10000 + ((ROOT_VERSION_CODE & 0xFF00) >> 8) * 100 +
263 (ROOT_VERSION_CODE & 0xFF)};
264 RUInt32BE fBEGIN{kBEGIN};
265 union {
266 struct {
267 RUInt32BE fEND{0};
268 RUInt32BE fSeekFree{0};
269 RUInt32BE fNbytesFree{0};
270 RUInt32BE fNfree{1};
271 RUInt32BE fNbytesName{0};
272 unsigned char fUnits{4};
273 RUInt32BE fCompress{0};
274 RUInt32BE fSeekInfo{0};
275 RUInt32BE fNbytesInfo{0};
276 } fInfoShort;
277 struct {
278 RUInt64BE fEND{0};
279 RUInt64BE fSeekFree{0};
280 RUInt32BE fNbytesFree{0};
281 RUInt32BE fNfree{1};
282 RUInt32BE fNbytesName{0};
283 unsigned char fUnits{8};
284 RUInt32BE fCompress{0};
285 RUInt64BE fSeekInfo{0};
286 RUInt32BE fNbytesInfo{0};
287 } fInfoLong;
288 };
289
290 RTFHeader() : fInfoShort() {}
291 RTFHeader(int compression) : fInfoShort() { fInfoShort.fCompress = compression; }
292
293 void SetBigFile()
294 {
295 if (fVersion >= kBigHeaderVersion)
296 return;
297
298 // clang-format off
299 std::uint32_t end = fInfoShort.fEND;
300 std::uint32_t seekFree = fInfoShort.fSeekFree;
301 std::uint32_t nbytesFree = fInfoShort.fNbytesFree;
302 std::uint32_t nFree = fInfoShort.fNfree;
303 std::uint32_t nbytesName = fInfoShort.fNbytesName;
304 std::uint32_t compress = fInfoShort.fCompress;
305 std::uint32_t seekInfo = fInfoShort.fSeekInfo;
306 std::uint32_t nbytesInfo = fInfoShort.fNbytesInfo;
307 fInfoLong.fEND = end;
308 fInfoLong.fSeekFree = seekFree;
309 fInfoLong.fNbytesFree = nbytesFree;
310 fInfoLong.fNfree = nFree;
311 fInfoLong.fNbytesName = nbytesName;
312 fInfoLong.fUnits = 8;
313 fInfoLong.fCompress = compress;
314 fInfoLong.fSeekInfo = seekInfo;
315 fInfoLong.fNbytesInfo = nbytesInfo;
316 fVersion = fVersion + kBigHeaderVersion;
317 // clang-format on
318 }
319
320 bool IsBigFile(std::uint64_t offset = 0) const
321 {
322 return (fVersion >= kBigHeaderVersion) ||
323 (offset > static_cast<unsigned int>(std::numeric_limits<std::int32_t>::max()));
324 }
325
326 std::uint32_t GetSize() const
327 {
328 std::uint32_t sizeHead = sizeof(fMagic) + sizeof(fVersion) + sizeof(fBEGIN);
329 if (IsBigFile())
330 return sizeHead + sizeof(fInfoLong);
331 return sizeHead + sizeof(fInfoShort);
332 }
333
334 std::uint64_t GetEnd() const
335 {
336 if (IsBigFile())
337 return fInfoLong.fEND;
338 return fInfoShort.fEND;
339 }
340
341 void SetEnd(std::uint64_t value)
342 {
343 if (IsBigFile(value)) {
344 SetBigFile();
345 fInfoLong.fEND = value;
346 } else {
347 fInfoShort.fEND = value;
348 }
349 }
350
351 std::uint64_t GetSeekFree() const
352 {
353 if (IsBigFile())
354 return fInfoLong.fSeekFree;
355 return fInfoShort.fSeekFree;
356 }
357
358 void SetSeekFree(std::uint64_t value)
359 {
360 if (IsBigFile(value)) {
361 SetBigFile();
362 fInfoLong.fSeekFree = value;
363 } else {
364 fInfoShort.fSeekFree = value;
365 }
366 }
367
368 void SetNbytesFree(std::uint32_t value)
369 {
370 if (IsBigFile()) {
371 fInfoLong.fNbytesFree = value;
372 } else {
373 fInfoShort.fNbytesFree = value;
374 }
375 }
376
377 void SetNbytesName(std::uint32_t value)
378 {
379 if (IsBigFile()) {
380 fInfoLong.fNbytesName = value;
381 } else {
382 fInfoShort.fNbytesName = value;
383 }
384 }
385
386 std::uint64_t GetSeekInfo() const
387 {
388 if (IsBigFile())
389 return fInfoLong.fSeekInfo;
390 return fInfoShort.fSeekInfo;
391 }
392
393 void SetSeekInfo(std::uint64_t value)
394 {
395 if (IsBigFile(value)) {
396 SetBigFile();
397 fInfoLong.fSeekInfo = value;
398 } else {
399 fInfoShort.fSeekInfo = value;
400 }
401 }
402
403 void SetNbytesInfo(std::uint32_t value)
404 {
405 if (IsBigFile()) {
406 fInfoLong.fNbytesInfo = value;
407 } else {
408 fInfoShort.fNbytesInfo = value;
409 }
410 }
411
412 void SetCompression(std::uint32_t value)
413 {
414 if (IsBigFile()) {
415 fInfoLong.fCompress = value;
416 } else {
417 fInfoShort.fCompress = value;
418 }
419 }
420};
421
422/// A reference to an unused byte-range in a TFile
423struct RTFFreeEntry {
424 static constexpr unsigned kBigFreeEntryVersion = 1000;
425
426 RUInt16BE fVersion{1};
427 union {
428 struct {
429 RUInt32BE fFirst{0};
430 RUInt32BE fLast{0};
431 } fInfoShort;
432 struct {
433 RUInt64BE fFirst{0};
434 RUInt64BE fLast{0};
435 } fInfoLong;
436 };
437
438 RTFFreeEntry() : fInfoShort() {}
439 void Set(std::uint64_t first, std::uint64_t last)
440 {
441 if (last > static_cast<unsigned int>(std::numeric_limits<std::int32_t>::max())) {
442 fVersion = fVersion + kBigFreeEntryVersion;
443 fInfoLong.fFirst = first;
444 fInfoLong.fLast = last;
445 } else {
446 fInfoShort.fFirst = first;
447 fInfoShort.fLast = last;
448 }
449 }
450 std::uint32_t GetSize() { return (fVersion >= kBigFreeEntryVersion) ? 18 : 10; }
451};
452
453/// The header of the directory key index
454struct RTFKeyList {
455 RUInt32BE fNKeys;
456 std::uint32_t GetSize() const { return sizeof(RTFKeyList); }
457 explicit RTFKeyList(std::uint32_t nKeys) : fNKeys(nKeys) {}
458};
459
460/// A streamed TDirectory (TFile) object
461struct RTFDirectory {
462 static constexpr unsigned kBigFileVersion = 1000;
463
464 RUInt16BE fClassVersion{5};
465 RTFDatetime fDateC;
466 RTFDatetime fDateM;
467 RUInt32BE fNBytesKeys{0};
468 RUInt32BE fNBytesName{0};
469 // The version of the key has to tell whether offsets are 32bit or 64bit long
470 union {
471 struct {
472 RUInt32BE fSeekDir{RTFHeader::kBEGIN};
473 RUInt32BE fSeekParent{0};
474 RUInt32BE fSeekKeys{0};
475 } fInfoShort;
476 struct {
477 RUInt64BE fSeekDir{RTFHeader::kBEGIN};
478 RUInt64BE fSeekParent{0};
479 RUInt64BE fSeekKeys{0};
480 } fInfoLong;
481 };
482
483 RTFDirectory() : fInfoShort() {}
484
485 // In case of a short TFile record (<2G), 3 padding ints are written after the UUID
486 std::uint32_t GetSize() const
487 {
488 if (fClassVersion >= kBigFileVersion)
489 return sizeof(RTFDirectory);
490 return 18 + sizeof(fInfoShort);
491 }
492
493 std::uint64_t GetSeekKeys() const
494 {
495 if (fClassVersion >= kBigFileVersion)
496 return fInfoLong.fSeekKeys;
497 return fInfoShort.fSeekKeys;
498 }
499
500 void SetSeekKeys(std::uint64_t seekKeys)
501 {
502 if (seekKeys > static_cast<unsigned int>(std::numeric_limits<std::int32_t>::max())) {
503 std::uint32_t seekDir = fInfoShort.fSeekDir;
504 std::uint32_t seekParent = fInfoShort.fSeekParent;
505 fInfoLong.fSeekDir = seekDir;
506 fInfoLong.fSeekParent = seekParent;
507 fInfoLong.fSeekKeys = seekKeys;
508 fClassVersion = fClassVersion + kBigFileVersion;
509 } else {
510 fInfoShort.fSeekKeys = seekKeys;
511 }
512 }
513};
514
515/// A zero UUID stored at the end of the TFile record
516struct RTFUUID {
517 RUInt16BE fVersionClass{1};
518 unsigned char fUUID[16];
519
520 RTFUUID()
521 {
522 TUUID uuid;
523 char *buffer = reinterpret_cast<char *>(this);
524 uuid.FillBuffer(buffer);
525 assert(reinterpret_cast<RTFUUID *>(buffer) <= (this + 1));
526 }
527 std::uint32_t GetSize() const { return sizeof(RTFUUID); }
528};
529
530/// A streamed RNTuple class
531///
532/// NOTE: this must be kept in sync with RNTuple.hxx.
533/// Aside ensuring consistency between the two classes' members, you need to make sure
534/// that fVersionClass matches the class version of RNTuple.
535struct RTFNTuple {
536 RUInt32BE fByteCount{0x40000000 | (sizeof(RTFNTuple) - sizeof(fByteCount))};
537 RUInt16BE fVersionClass{2};
538 RUInt16BE fVersionEpoch{0};
539 RUInt16BE fVersionMajor{0};
540 RUInt16BE fVersionMinor{0};
541 RUInt16BE fVersionPatch{0};
542 RUInt64BE fSeekHeader{0};
543 RUInt64BE fNBytesHeader{0};
544 RUInt64BE fLenHeader{0};
545 RUInt64BE fSeekFooter{0};
546 RUInt64BE fNBytesFooter{0};
547 RUInt64BE fLenFooter{0};
548 RUInt64BE fMaxKeySize{0};
549
550 static constexpr std::uint32_t GetSizePlusChecksum() { return sizeof(RTFNTuple) + sizeof(std::uint64_t); }
551
552 RTFNTuple() = default;
553 explicit RTFNTuple(const ROOT::RNTuple &inMemoryAnchor)
554 {
555 fVersionEpoch = inMemoryAnchor.GetVersionEpoch();
556 fVersionMajor = inMemoryAnchor.GetVersionMajor();
557 fVersionMinor = inMemoryAnchor.GetVersionMinor();
558 fVersionPatch = inMemoryAnchor.GetVersionPatch();
559 fSeekHeader = inMemoryAnchor.GetSeekHeader();
560 fNBytesHeader = inMemoryAnchor.GetNBytesHeader();
561 fLenHeader = inMemoryAnchor.GetLenHeader();
562 fSeekFooter = inMemoryAnchor.GetSeekFooter();
563 fNBytesFooter = inMemoryAnchor.GetNBytesFooter();
564 fLenFooter = inMemoryAnchor.GetLenFooter();
565 fMaxKeySize = inMemoryAnchor.GetMaxKeySize();
566 }
567 std::uint32_t GetSize() const { return sizeof(RTFNTuple); }
568 // The byte count and class version members are not checksummed
569 std::uint32_t GetOffsetCkData() { return sizeof(fByteCount) + sizeof(fVersionClass); }
570 std::uint32_t GetSizeCkData() { return GetSize() - GetOffsetCkData(); }
571 unsigned char *GetPtrCkData() { return reinterpret_cast<unsigned char *>(this) + GetOffsetCkData(); }
572};
573
574/// The bare file global header
575struct RBareFileHeader {
576 char fMagic[7]{'r', 'n', 't', 'u', 'p', 'l', 'e'};
577 RUInt32BE fRootVersion{(ROOT_VERSION_CODE >> 16) * 10000 + ((ROOT_VERSION_CODE & 0xFF00) >> 8) * 100 +
578 (ROOT_VERSION_CODE & 0xFF)};
579 RUInt32BE fFormatVersion{1};
580 RUInt32BE fCompress{0};
581 RTFNTuple fNTuple;
582 // followed by the ntuple name
583};
584#pragma pack(pop)
585
586/// The artifical class name shown for opaque RNTuple keys (see TBasket)
587constexpr char const *kBlobClassName = "RBlob";
588/// The class name of the RNTuple anchor
589constexpr char const *kNTupleClassName = "ROOT::RNTuple";
590
591} // anonymous namespace
592
593namespace ROOT {
594namespace Experimental {
595namespace Internal {
596/// If a TFile container is written by a C stream (simple file), on dataset commit, the file header
597/// and the TFile record need to be updated
599 RTFHeader fHeader;
600 RTFDirectory fFileRecord;
601 std::uint64_t fSeekNTuple{0}; // Remember the offset for the keys list
602 std::uint64_t fSeekFileRecord{0};
603};
604
605/// The RKeyBlob writes an invisible key into a TFile. That is, a key that is not indexed in the list of keys,
606/// like a TBasket.
607/// NOTE: out of anonymous namespace because otherwise ClassDefInline fails to compile
608/// on some platforms.
609class RKeyBlob : public TKey {
610public:
611 RKeyBlob() = default;
612
613 explicit RKeyBlob(TFile *file) : TKey(file)
614 {
616 fVersion += RTFKey::kBigKeyVersion;
617 fKeylen = Sizeof();
618 }
619
620 /// Register a new key for a data record of size nbytes
621 void Reserve(size_t nbytes, std::uint64_t *seekKey)
622 {
623 Create(nbytes);
624 *seekKey = fSeekKey;
625 }
626
627 bool WasAllocatedInAFreeSlot() const { return fLeft > 0; }
628
630};
631
632} // namespace Internal
633} // namespace Experimental
634} // namespace ROOT
635
636// Computes how many chunks do we need to fit `nbytes` of payload, considering that the
637// first chunk also needs to house the offsets of the other chunks and no chunk can
638// be bigger than `maxChunkSize`. When saved to a TFile, each chunk is part of a separate TKey.
639static size_t ComputeNumChunks(size_t nbytes, size_t maxChunkSize)
640{
641 constexpr size_t kChunkOffsetSize = sizeof(std::uint64_t);
642
644 size_t nChunks = (nbytes + maxChunkSize - 1) / maxChunkSize;
645 assert(nChunks > 1);
646 size_t nbytesTail = nbytes % maxChunkSize;
647 size_t nbytesExtra = (nbytesTail > 0) * (maxChunkSize - nbytesTail);
650 ++nChunks;
652 }
653
654 // We don't support having more chunkOffsets than what fits in one chunk.
655 // For a reasonable-sized maxKeySize it looks very unlikely that we can have more chunks
656 // than we can fit in the first `maxKeySize` bytes. E.g. for maxKeySize = 1GiB we can fit
657 // 134217728 chunk offsets, making our multi-key blob's capacity exactly 128 PiB.
659
660 return nChunks;
661}
662
664
666{
667 char ident[4];
668 ReadBuffer(ident, 4, 0);
669 if (std::string(ident, 4) == "root")
670 return GetNTupleProper(ntupleName);
671 fIsBare = true;
672 return GetNTupleBare(ntupleName);
673}
674
675/// Searches for a key with the given name and type in the key index of the given directory.
676/// Return 0 if the key was not found.
678 std::string_view keyName,
679 std::string_view typeName)
680{
681 RTFDirectory directory;
683
684 RTFKey key;
685 RUInt32BE nKeys;
686 std::uint64_t offset = directory.GetSeekKeys();
687 ReadBuffer(&key, sizeof(key), offset);
688 offset += key.fKeyLen;
689 ReadBuffer(&nKeys, sizeof(nKeys), offset);
690 offset += sizeof(nKeys);
691
692 for (unsigned int i = 0; i < nKeys; ++i) {
693 ReadBuffer(&key, sizeof(key), offset);
694 auto offsetNextKey = offset + key.fKeyLen;
695
696 offset += key.GetHeaderSize();
697 RTFString name;
698 ReadBuffer(&name, 1, offset);
699 ReadBuffer(&name, name.GetSize(), offset);
700 if (std::string_view(name.fData, name.fLName) != typeName) {
702 continue;
703 }
704 offset += name.GetSize();
705 ReadBuffer(&name, 1, offset);
706 ReadBuffer(&name, name.GetSize(), offset);
707 if (std::string_view(name.fData, name.fLName) == keyName) {
708 return key.GetSeekKey();
709 }
711 }
712
713 // Not found
714 return 0;
715}
716
718{
719 RTFHeader fileHeader;
720 ReadBuffer(&fileHeader, sizeof(fileHeader), 0);
721
722 RTFKey key;
723 RTFString name;
724 ReadBuffer(&key, sizeof(key), fileHeader.fBEGIN);
725 // Skip over the entire key length, including the class name, object name, and title stored in it.
726 std::uint64_t offset = fileHeader.fBEGIN + key.fKeyLen;
727 // Skip over the name and title of the TNamed preceding the TFile (root TDirectory) entry.
728 ReadBuffer(&name, 1, offset);
729 offset += name.GetSize();
730 ReadBuffer(&name, 1, offset);
731 offset += name.GetSize();
732
733 // split ntupleName by '/' character to open datasets in subdirectories.
734 std::string ntuplePathTail(ntuplePath);
735 if (!ntuplePathTail.empty() && ntuplePathTail[0] == '/')
736 ntuplePathTail = ntuplePathTail.substr(1);
737 auto pos = std::string::npos;
738 while ((pos = ntuplePathTail.find('/')) != std::string::npos) {
739 auto directoryName = ntuplePathTail.substr(0, pos);
740 ntuplePathTail.erase(0, pos + 1);
741
742 offset = SearchInDirectory(offset, directoryName, "TDirectory");
743 if (offset == 0) {
744 return R__FAIL("no directory named '" + std::string(directoryName) + "' in file '" + fRawFile->GetUrl() + "'");
745 }
746 ReadBuffer(&key, sizeof(key), offset);
747 offset = key.GetSeekKey() + key.fKeyLen;
748 }
749 // no more '/' delimiter in ntuplePath
751
752 offset = SearchInDirectory(offset, ntupleName, kNTupleClassName);
753 if (offset == 0) {
754 return R__FAIL("no RNTuple named '" + std::string(ntupleName) + "' in file '" + fRawFile->GetUrl() + "'");
755 }
756
757 ReadBuffer(&key, sizeof(key), offset);
758 offset = key.GetSeekKey() + key.fKeyLen;
759
760 // size of a RTFNTuple version 2 (min supported version); future anchor versions can grow.
761 constexpr size_t kMinNTupleSize = 78;
762 static_assert(kMinNTupleSize == RTFNTuple::GetSizePlusChecksum());
763 if (key.fObjLen < kMinNTupleSize) {
764 return R__FAIL("invalid anchor size: " + std::to_string(key.fObjLen) + " < " + std::to_string(sizeof(RTFNTuple)));
765 }
766 // The object length can be smaller than the size of RTFNTuple if it comes from a past RNTuple class version,
767 // or larger than it if it comes from a future RNTuple class version.
768 auto bufAnchor = MakeUninitArray<unsigned char>(std::max<size_t>(key.fObjLen, sizeof(RTFNTuple)));
769 RTFNTuple *ntuple = new (bufAnchor.get()) RTFNTuple;
770
771 auto objNbytes = key.GetSize() - key.fKeyLen;
773 if (objNbytes != key.fObjLen) {
775 decompressor.Unzip(bufAnchor.get(), objNbytes, key.fObjLen);
776 }
777
778 // We require that future class versions only append members and store the checksum in the last 8 bytes
779 // Checksum calculation: strip byte count, class version, fChecksum member
780 auto lenCkData = key.fObjLen - ntuple->GetOffsetCkData() - sizeof(uint64_t);
781 auto ckCalc = XXH3_64bits(ntuple->GetPtrCkData(), lenCkData);
782 uint64_t ckOnDisk;
783
784 RUInt64BE *ckOnDiskPtr = reinterpret_cast<RUInt64BE *>(bufAnchor.get() + key.fObjLen - sizeof(uint64_t));
785 ckOnDisk = static_cast<uint64_t>(*ckOnDiskPtr);
786 if (ckCalc != ckOnDisk) {
787 return R__FAIL("RNTuple anchor checksum mismatch");
788 }
789
790 return CreateAnchor(ntuple->fVersionEpoch, ntuple->fVersionMajor, ntuple->fVersionMinor, ntuple->fVersionPatch,
791 ntuple->fSeekHeader, ntuple->fNBytesHeader, ntuple->fLenHeader, ntuple->fSeekFooter,
792 ntuple->fNBytesFooter, ntuple->fLenFooter, ntuple->fMaxKeySize);
793}
794
796{
797 RBareFileHeader fileHeader;
798 ReadBuffer(&fileHeader, sizeof(fileHeader), 0);
799 RTFString name;
800 auto offset = sizeof(fileHeader);
801 ReadBuffer(&name, 1, offset);
802 ReadBuffer(&name, name.GetSize(), offset);
803 std::string_view foundName(name.fData, name.fLName);
804 if (foundName != ntupleName) {
805 return R__FAIL("expected RNTuple named '" + std::string(ntupleName) + "' but instead found '" +
806 std::string(foundName) + "' in file '" + fRawFile->GetUrl() + "'");
807 }
808 offset += name.GetSize();
809
810 RTFNTuple ntuple;
811 ReadBuffer(&ntuple, sizeof(ntuple), offset);
812 std::uint64_t onDiskChecksum;
814 auto checksum = XXH3_64bits(ntuple.GetPtrCkData(), ntuple.GetSizeCkData());
815 if (checksum != static_cast<uint64_t>(onDiskChecksum))
816 return R__FAIL("RNTuple bare file: anchor checksum mismatch");
817
818 return CreateAnchor(ntuple.fVersionEpoch, ntuple.fVersionMajor, ntuple.fVersionMinor, ntuple.fVersionPatch,
819 ntuple.fSeekHeader, ntuple.fNBytesHeader, ntuple.fLenHeader, ntuple.fSeekFooter,
820 ntuple.fNBytesFooter, ntuple.fLenFooter, ntuple.fMaxKeySize);
821}
822
824{
825 size_t nread;
826 if (fMaxKeySize == 0 || nbytes <= fMaxKeySize) {
827 // Fast path: read single blob
828 nread = fRawFile->ReadAt(buffer, nbytes, offset);
829 } else {
830 // Read chunked blob. See RNTupleFileWriter::WriteBlob() for details.
831 const size_t nChunks = ComputeNumChunks(nbytes, fMaxKeySize);
832 const size_t nbytesChunkOffsets = (nChunks - 1) * sizeof(std::uint64_t);
833 const size_t nbytesFirstChunk = fMaxKeySize - nbytesChunkOffsets;
834 uint8_t *bufCur = reinterpret_cast<uint8_t *>(buffer);
835
836 // Read first chunk
837 nread = fRawFile->ReadAt(bufCur, fMaxKeySize, offset);
838 R__ASSERT(nread == fMaxKeySize);
839 // NOTE: we read the entire chunk in `bufCur`, but we only advance the pointer by `nbytesFirstChunk`,
840 // since the last part of `bufCur` will later be overwritten by the next chunk's payload.
841 // We do this to avoid a second ReadAt to read in the chunk offsets.
844
847
849 std::uint64_t *curChunkOffset = &chunkOffsets[0];
850
851 do {
852 std::uint64_t chunkOffset;
855
856 const size_t bytesToRead = std::min<size_t>(fMaxKeySize, remainingBytes);
857 // Ensure we don't read outside of the buffer
858 R__ASSERT(static_cast<size_t>(bufCur - reinterpret_cast<uint8_t *>(buffer)) <= nbytes - bytesToRead);
859
860 auto nbytesRead = fRawFile->ReadAt(bufCur, bytesToRead, chunkOffset);
862
866 } while (remainingBytes > 0);
867 }
869}
870
871////////////////////////////////////////////////////////////////////////////////
872
874
875/// Prepare a blob key in the provided buffer, which must provide space for kBlobKeyLen bytes. Note that the array type
876/// is purely documentation, the argument is actually just a pointer.
877static void PrepareBlobKey(std::int64_t offset, size_t nbytes, size_t len, unsigned char buffer[kBlobKeyLen])
878{
879 RTFString strClass{kBlobClassName};
880 RTFString strObject;
881 RTFString strTitle;
882 RTFKey keyHeader(offset, RTFHeader::kBEGIN, strClass, strObject, strTitle, len, nbytes);
883 R__ASSERT(keyHeader.fKeyLen == kBlobKeyLen);
884
885 // Copy structures into the buffer.
886 unsigned char *writeBuffer = buffer;
887 memcpy(writeBuffer, &keyHeader, keyHeader.GetHeaderSize());
888 writeBuffer += keyHeader.GetHeaderSize();
889 memcpy(writeBuffer, &strClass, strClass.GetSize());
890 writeBuffer += strClass.GetSize();
892 writeBuffer += strObject.GetSize();
893 memcpy(writeBuffer, &strTitle, strTitle.GetSize());
894 writeBuffer += strTitle.GetSize();
895 R__ASSERT(writeBuffer == buffer + kBlobKeyLen);
896}
897
898////////////////////////////////////////////////////////////////////////////////
899
901
903{
904 static_assert(kHeaderBlockSize % kBlockAlign == 0, "invalid header block size");
905 if (bufferSize % kBlockAlign != 0)
906 throw RException(R__FAIL("Buffer size not a multiple of alignment: " + std::to_string(bufferSize)));
907 fBlockSize = bufferSize;
908
909 std::align_val_t blockAlign{kBlockAlign};
910 fHeaderBlock = static_cast<unsigned char *>(::operator new[](kHeaderBlockSize, blockAlign));
911 memset(fHeaderBlock, 0, kHeaderBlockSize);
912 fBlock = static_cast<unsigned char *>(::operator new[](fBlockSize, blockAlign));
913 memset(fBlock, 0, fBlockSize);
914}
915
917{
918 if (fFile)
919 fclose(fFile);
920
921 std::align_val_t blockAlign{kBlockAlign};
922 if (fHeaderBlock)
923 ::operator delete[](fHeaderBlock, blockAlign);
924 if (fBlock)
925 ::operator delete[](fBlock, blockAlign);
926}
927
928namespace {
929int FSeek64(FILE *stream, std::int64_t offset, int origin)
930{
931#ifdef R__SEEK64
932 return fseeko64(stream, offset, origin);
933#else
934 return fseek(stream, offset, origin);
935#endif
936}
937} // namespace
938
940{
941 // Write the last partially filled block, which may still need appropriate alignment for Direct I/O.
942 // If it is the first block, get the updated header block.
943 if (fBlockOffset == 0) {
944 std::size_t headerBlockSize = kHeaderBlockSize;
945 if (headerBlockSize > fFilePos) {
946 headerBlockSize = fFilePos;
947 }
948 memcpy(fBlock, fHeaderBlock, headerBlockSize);
949 }
950
951 std::size_t retval = FSeek64(fFile, fBlockOffset, SEEK_SET);
952 if (retval)
953 throw RException(R__FAIL(std::string("Seek failed: ") + strerror(errno)));
954
955 std::size_t lastBlockSize = fFilePos - fBlockOffset;
956 R__ASSERT(lastBlockSize <= fBlockSize);
957 if (fDirectIO) {
958 // Round up to a multiple of kBlockAlign.
959 lastBlockSize += kBlockAlign - 1;
960 lastBlockSize = (lastBlockSize / kBlockAlign) * kBlockAlign;
961 R__ASSERT(lastBlockSize <= fBlockSize);
962 }
963 retval = fwrite(fBlock, 1, lastBlockSize, fFile);
964 if (retval != lastBlockSize)
965 throw RException(R__FAIL(std::string("write failed: ") + strerror(errno)));
966
967 // Write the (updated) header block, unless it was part of the write above.
968 if (fBlockOffset > 0) {
969 retval = FSeek64(fFile, 0, SEEK_SET);
970 if (retval)
971 throw RException(R__FAIL(std::string("Seek failed: ") + strerror(errno)));
972
973 retval = fwrite(fHeaderBlock, 1, kHeaderBlockSize, fFile);
974 if (retval != RFileSimple::kHeaderBlockSize)
975 throw RException(R__FAIL(std::string("write failed: ") + strerror(errno)));
976 }
977
978 retval = fflush(fFile);
979 if (retval)
980 throw RException(R__FAIL(std::string("Flush failed: ") + strerror(errno)));
981}
982
984 std::int64_t offset)
985{
986 R__ASSERT(fFile);
987 size_t retval;
988 if ((offset >= 0) && (static_cast<std::uint64_t>(offset) != fFilePos)) {
989 fFilePos = offset;
990 }
991
992 // Keep header block to overwrite on commit.
993 if (fFilePos < kHeaderBlockSize) {
994 std::size_t headerBytes = nbytes;
995 if (fFilePos + headerBytes > kHeaderBlockSize) {
996 headerBytes = kHeaderBlockSize - fFilePos;
997 }
998 memcpy(fHeaderBlock + fFilePos, buffer, headerBytes);
999 }
1000
1001 R__ASSERT(fFilePos >= fBlockOffset);
1002
1003 while (nbytes > 0) {
1004 std::uint64_t posInBlock = fFilePos % fBlockSize;
1005 std::uint64_t blockOffset = fFilePos - posInBlock;
1006 if (blockOffset != fBlockOffset) {
1007 // Write the block.
1008 retval = FSeek64(fFile, fBlockOffset, SEEK_SET);
1009 if (retval)
1010 throw RException(R__FAIL(std::string("Seek failed: ") + strerror(errno)));
1011
1012 retval = fwrite(fBlock, 1, fBlockSize, fFile);
1013 if (retval != fBlockSize)
1014 throw RException(R__FAIL(std::string("write failed: ") + strerror(errno)));
1015
1016 // Null the buffer contents for good measure.
1017 memset(fBlock, 0, fBlockSize);
1018 }
1019
1020 fBlockOffset = blockOffset;
1021 std::size_t blockSize = nbytes;
1022 if (blockSize > fBlockSize - posInBlock) {
1023 blockSize = fBlockSize - posInBlock;
1024 }
1025 memcpy(fBlock + posInBlock, buffer, blockSize);
1026 buffer = static_cast<const unsigned char *>(buffer) + blockSize;
1027 nbytes -= blockSize;
1028 fFilePos += blockSize;
1029 }
1030}
1031
1033 const void *buffer, std::size_t nbytes, std::size_t len, std::int64_t offset, std::uint64_t directoryOffset,
1034 const std::string &className, const std::string &objectName, const std::string &title)
1035{
1036 if (offset > 0)
1037 fKeyOffset = offset;
1038 RTFString strClass{className};
1039 RTFString strObject{objectName};
1040 RTFString strTitle{title};
1041
1042 RTFKey key(fKeyOffset, directoryOffset, strClass, strObject, strTitle, len, nbytes);
1043 Write(&key, key.GetHeaderSize(), fKeyOffset);
1044 Write(&strClass, strClass.GetSize());
1045 Write(&strObject, strObject.GetSize());
1046 Write(&strTitle, strTitle.GetSize());
1047 auto offsetData = fFilePos;
1048 // The next key starts after the data.
1049 fKeyOffset = offsetData + nbytes;
1050 if (buffer)
1051 Write(buffer, nbytes);
1052
1053 return offsetData;
1054}
1055
1056std::uint64_t
1058 unsigned char keyBuffer[kBlobKeyLen])
1059{
1060 if (keyBuffer) {
1061 PrepareBlobKey(fKeyOffset, nbytes, len, keyBuffer);
1062 } else {
1063 unsigned char localKeyBuffer[kBlobKeyLen];
1064 PrepareBlobKey(fKeyOffset, nbytes, len, localKeyBuffer);
1065 Write(localKeyBuffer, kBlobKeyLen, fKeyOffset);
1066 }
1067
1068 auto offsetData = fKeyOffset + kBlobKeyLen;
1069 // The next key starts after the data.
1070 fKeyOffset = offsetData + nbytes;
1071
1072 return offsetData;
1073}
1074
1075////////////////////////////////////////////////////////////////////////////////
1076
1078 std::int64_t offset)
1079{
1080 fDirectory->GetFile()->Seek(offset);
1081 bool rv = fDirectory->GetFile()->WriteBuffer((char *)(buffer), nbytes);
1082 if (rv)
1083 throw RException(R__FAIL("WriteBuffer failed."));
1084}
1085
1086std::uint64_t
1088 unsigned char keyBuffer[kBlobKeyLen])
1089{
1090 std::uint64_t offsetKey;
1091 RKeyBlob keyBlob(fDirectory->GetFile());
1092 // Since it is unknown beforehand if offsetKey is beyond the 2GB limit or not,
1093 // RKeyBlob will always reserve space for a big key (version >= 1000)
1094 keyBlob.Reserve(nbytes, &offsetKey);
1095
1096 if (keyBuffer) {
1098 } else {
1099 unsigned char localKeyBuffer[kBlobKeyLen];
1102 }
1103
1104 if (keyBlob.WasAllocatedInAFreeSlot()) {
1105 // If the key was allocated in a free slot, the last 4 bytes of its buffer contain the new size
1106 // of the remaining free slot and we need to write it to disk before the key gets destroyed at the end of the
1107 // function.
1108 Write(keyBlob.GetBuffer() + nbytes, sizeof(Int_t), offsetKey + kBlobKeyLen + nbytes);
1109 }
1110
1112
1113 return offsetData;
1114}
1115
1116////////////////////////////////////////////////////////////////////////////////
1117
1119 : fNTupleName(name)
1120{
1121 fFileSimple.fControlBlock = std::make_unique<ROOT::Experimental::Internal::RTFileControlBlock>();
1123 auto infoRNTuple = RNTuple::Class()->GetStreamerInfo();
1125}
1126
1128
1129std::unique_ptr<ROOT::Experimental::Internal::RNTupleFileWriter>
1132 const RNTupleWriteOptions &options)
1133{
1134 std::string fileName(path);
1135 size_t idxDirSep = fileName.find_last_of("\\/");
1136 if (idxDirSep != std::string::npos) {
1137 fileName.erase(0, idxDirSep + 1);
1138 }
1139#ifdef R__LINUX
1140 int flags = O_WRONLY | O_CREAT | O_TRUNC;
1141#ifdef O_LARGEFILE
1142 // Add the equivalent flag that is passed by fopen64.
1143 flags |= O_LARGEFILE;
1144#endif
1145 if (options.GetUseDirectIO()) {
1146 flags |= O_DIRECT;
1147 }
1148 int fd = open(std::string(path).c_str(), flags, 0666);
1149 FILE *fileStream = fdopen(fd, "wb");
1150#else
1151#ifdef R__SEEK64
1152 FILE *fileStream = fopen64(std::string(path.data(), path.size()).c_str(), "wb");
1153#else
1154 FILE *fileStream = fopen(std::string(path.data(), path.size()).c_str(), "wb");
1155#endif
1156#endif
1158 // RNTupleFileWriter::RFileSimple does its own buffering, turn off additional buffering from C stdio.
1159 std::setvbuf(fileStream, nullptr, _IONBF, 0);
1160
1161 auto writer = std::unique_ptr<RNTupleFileWriter>(new RNTupleFileWriter(ntupleName, options.GetMaxKeySize()));
1162 writer->fFileSimple.fFile = fileStream;
1163 writer->fFileSimple.fDirectIO = options.GetUseDirectIO();
1164 writer->fFileSimple.AllocateBuffers(options.GetWriteBufferSize());
1165 writer->fFileName = fileName;
1166
1167 int defaultCompression = options.GetCompression();
1168 switch (containerFormat) {
1169 case EContainerFormat::kTFile: writer->WriteTFileSkeleton(defaultCompression); break;
1170 case EContainerFormat::kBare:
1171 writer->fIsBare = true;
1172 writer->WriteBareFileSkeleton(defaultCompression);
1173 break;
1174 default: R__ASSERT(false && "Internal error: unhandled container format");
1175 }
1176
1177 return writer;
1178}
1179
1180std::unique_ptr<ROOT::Experimental::Internal::RNTupleFileWriter>
1182 std::uint64_t maxKeySize)
1183{
1184 TFile *file = fileOrDirectory.GetFile();
1185 if (!file)
1186 throw RException(R__FAIL("invalid attempt to add an RNTuple to a directory that is not backed by a file"));
1187 assert(file->IsBinary());
1188
1189 auto writer = std::unique_ptr<RNTupleFileWriter>(new RNTupleFileWriter(ntupleName, maxKeySize));
1190 writer->fFileProper.fDirectory = &fileOrDirectory;
1191 return writer;
1192}
1193
1199
1201{
1202 if (fFileProper) {
1203 // Easy case, the ROOT file header and the RNTuple streaming is taken care of by TFile
1204 fFileProper.fDirectory->WriteObject(&fNTupleAnchor, fNTupleName.c_str());
1205
1206 // Make sure the streamer info records used in the RNTuple are written to the file
1208 buf.SetParent(fFileProper.fDirectory->GetFile());
1209 for (auto [_, info] : fStreamerInfoMap)
1210 buf.TagStreamerInfo(info);
1211
1212 fFileProper.fDirectory->GetFile()->Write();
1213 return;
1214 }
1215
1216 // Writing by C file stream: prepare the container format header and stream the RNTuple anchor object
1217 R__ASSERT(fFileSimple);
1218
1219 if (fIsBare) {
1220 RTFNTuple ntupleOnDisk(fNTupleAnchor);
1221 // Compute the checksum
1222 std::uint64_t checksum = XXH3_64bits(ntupleOnDisk.GetPtrCkData(), ntupleOnDisk.GetSizeCkData());
1223 memcpy(fFileSimple.fHeaderBlock + fFileSimple.fControlBlock->fSeekNTuple, &ntupleOnDisk, ntupleOnDisk.GetSize());
1224 memcpy(fFileSimple.fHeaderBlock + fFileSimple.fControlBlock->fSeekNTuple + ntupleOnDisk.GetSize(), &checksum,
1225 sizeof(checksum));
1226 fFileSimple.Flush();
1227 return;
1228 }
1229
1230 auto anchorSize = WriteTFileNTupleKey(compression);
1231 WriteTFileKeysList(anchorSize); // NOTE: this is written uncompressed
1232 WriteTFileStreamerInfo(compression);
1233 WriteTFileFreeList(); // NOTE: this is written uncompressed
1234
1235 // Update header and TFile record
1236 memcpy(fFileSimple.fHeaderBlock, &fFileSimple.fControlBlock->fHeader, fFileSimple.fControlBlock->fHeader.GetSize());
1237 R__ASSERT(fFileSimple.fControlBlock->fSeekFileRecord + fFileSimple.fControlBlock->fFileRecord.GetSize() <
1238 RFileSimple::kHeaderBlockSize);
1239 memcpy(fFileSimple.fHeaderBlock + fFileSimple.fControlBlock->fSeekFileRecord,
1240 &fFileSimple.fControlBlock->fFileRecord, fFileSimple.fControlBlock->fFileRecord.GetSize());
1241
1242 fFileSimple.Flush();
1243}
1244
1246{
1247 auto writeKey = [this](const void *payload, size_t nBytes, size_t length) {
1248 std::uint64_t offset = ReserveBlob(nBytes, length);
1249 WriteIntoReservedBlob(payload, nBytes, offset);
1250 return offset;
1251 };
1252
1253 const std::uint64_t maxKeySize = fNTupleAnchor.fMaxKeySize;
1254 R__ASSERT(maxKeySize > 0);
1255 // We don't need the object length except for seeing compression ratios in TFile::Map()
1256 // Make sure that the on-disk object length fits into the TKey header.
1257 if (static_cast<std::uint64_t>(len) > static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()))
1258 len = nbytes;
1259
1260 if (nbytes <= maxKeySize) {
1261 // Fast path: only write 1 key.
1262 return writeKey(data, nbytes, len);
1263 }
1264
1265 /**
1266 * Writing a key bigger than the max allowed size. In this case we split the payload
1267 * into multiple keys, reserving the end of the first key payload for pointers to the
1268 * next ones. E.g. if a key needs to be split into 3 chunks, the first chunk will have
1269 * the format:
1270 * +--------------------+
1271 * | |
1272 * | Data |
1273 * |--------------------|
1274 * | pointer to chunk 2 |
1275 * | pointer to chunk 3 |
1276 * +--------------------+
1277 */
1278 const size_t nChunks = ComputeNumChunks(nbytes, maxKeySize);
1279 const size_t nbytesChunkOffsets = (nChunks - 1) * sizeof(std::uint64_t);
1281 // Skip writing the first chunk, it will be written last (in the file) below.
1282
1283 const uint8_t *chunkData = reinterpret_cast<const uint8_t *>(data) + nbytesFirstChunk;
1285
1287 std::uint64_t chunkOffsetIdx = 0;
1288
1289 do {
1290 const size_t bytesNextChunk = std::min<size_t>(remainingBytes, maxKeySize);
1291 const std::uint64_t offset = writeKey(chunkData, bytesNextChunk, bytesNextChunk);
1292
1295
1298
1299 } while (remainingBytes > 0);
1300
1301 // Write the first key, with part of the data and the pointers to (logically) following keys appended.
1302 const std::uint64_t firstOffset = ReserveBlob(maxKeySize, maxKeySize);
1303 WriteIntoReservedBlob(data, nbytesFirstChunk, firstOffset);
1304 const std::uint64_t chunkOffsetsOffset = firstOffset + nbytesFirstChunk;
1305 WriteIntoReservedBlob(chunkOffsetsToWrite.get(), nbytesChunkOffsets, chunkOffsetsOffset);
1306
1307 return firstOffset;
1308}
1309
1311 unsigned char keyBuffer[kBlobKeyLen])
1312{
1313 // ReserveBlob cannot be used to reserve a multi-key blob
1314 R__ASSERT(nbytes <= fNTupleAnchor.GetMaxKeySize());
1315
1316 std::uint64_t offset;
1317 if (fFileSimple) {
1318 if (fIsBare) {
1319 offset = fFileSimple.fKeyOffset;
1320 fFileSimple.fKeyOffset += nbytes;
1321 } else {
1322 offset = fFileSimple.ReserveBlobKey(nbytes, len, keyBuffer);
1323 }
1324 } else {
1325 offset = fFileProper.ReserveBlobKey(nbytes, len, keyBuffer);
1326 }
1327 return offset;
1328}
1329
1331 std::int64_t offset)
1332{
1333 if (fFileSimple) {
1334 fFileSimple.Write(buffer, nbytes, offset);
1335 } else {
1336 fFileProper.Write(buffer, nbytes, offset);
1337 }
1338}
1339
1340std::uint64_t
1342{
1343 auto offset = WriteBlob(data, nbytes, lenHeader);
1344 fNTupleAnchor.fLenHeader = lenHeader;
1345 fNTupleAnchor.fNBytesHeader = nbytes;
1346 fNTupleAnchor.fSeekHeader = offset;
1347 return offset;
1348}
1349
1350std::uint64_t
1352{
1353 auto offset = WriteBlob(data, nbytes, lenFooter);
1354 fNTupleAnchor.fLenFooter = lenFooter;
1355 fNTupleAnchor.fNBytesFooter = nbytes;
1356 fNTupleAnchor.fSeekFooter = offset;
1357 return offset;
1358}
1359
1361{
1362 RBareFileHeader bareHeader;
1363 bareHeader.fCompress = defaultCompression;
1364 fFileSimple.Write(&bareHeader, sizeof(bareHeader), 0);
1365 RTFString ntupleName{fNTupleName};
1366 fFileSimple.Write(&ntupleName, ntupleName.GetSize());
1367
1368 // Write zero-initialized ntuple to reserve the space; will be overwritten on commit
1369 RTFNTuple ntupleOnDisk;
1370 fFileSimple.fControlBlock->fSeekNTuple = fFileSimple.fFilePos;
1371 fFileSimple.Write(&ntupleOnDisk, ntupleOnDisk.GetSize());
1372 std::uint64_t checksum = 0;
1373 fFileSimple.Write(&checksum, sizeof(checksum));
1374 fFileSimple.fKeyOffset = fFileSimple.fFilePos;
1375}
1376
1378{
1379 // The streamer info record is a TList of TStreamerInfo object. We cannot use
1380 // RNTupleSerializer::SerializeStreamerInfos because that uses TBufferIO::WriteObject.
1381 // This would prepend the streamed TList with self-decription information.
1382 // The streamer info record is just the streamed TList.
1383
1385 for (auto [_, info] : fStreamerInfoMap) {
1387 }
1388
1389 // We will stream the list with a TBufferFile. When reading the streamer info records back,
1390 // the read buffer includes the key and the streamed list. Therefore, we need to start streaming
1391 // with an offset of the key length. Otherwise, the offset for referencing duplicate objects in the
1392 // buffer will point to the wrong places.
1393
1394 // Figure out key length
1395 RTFString strTList{"TList"};
1396 RTFString strStreamerInfo{"StreamerInfo"};
1397 RTFString strStreamerTitle{"Doubly linked list"};
1398 fFileSimple.fControlBlock->fHeader.SetSeekInfo(fFileSimple.fKeyOffset);
1399 auto keyLen = RTFKey(fFileSimple.fControlBlock->fHeader.GetSeekInfo(), RTFHeader::kBEGIN, strTList, strStreamerInfo,
1401 .fKeyLen;
1402
1403 TBufferFile buffer(TBuffer::kWrite, keyLen + 1);
1404 buffer.SetBufferOffset(keyLen);
1405 streamerInfoList.Streamer(buffer);
1406 assert(buffer.Length() > keyLen);
1407 const auto bufPayload = buffer.Buffer() + keyLen;
1408 const auto lenPayload = buffer.Length() - keyLen;
1409
1412
1413 fFileSimple.WriteKey(zipStreamerInfos.get(), szZipStreamerInfos, lenPayload,
1414 fFileSimple.fControlBlock->fHeader.GetSeekInfo(), RTFHeader::kBEGIN, "TList", "StreamerInfo",
1415 "Doubly linked list");
1416 fFileSimple.fControlBlock->fHeader.SetNbytesInfo(fFileSimple.fFilePos -
1417 fFileSimple.fControlBlock->fHeader.GetSeekInfo());
1418}
1419
1421{
1422 RTFString strEmpty;
1423 RTFString strRNTupleClass{"ROOT::RNTuple"};
1424 RTFString strRNTupleName{fNTupleName};
1425 RTFString strFileName{fFileName};
1426
1427 RTFKey keyRNTuple(fFileSimple.fControlBlock->fSeekNTuple, RTFHeader::kBEGIN, strRNTupleClass, strRNTupleName,
1428 strEmpty, RTFNTuple::GetSizePlusChecksum(), anchorSize);
1429
1430 fFileSimple.fControlBlock->fFileRecord.SetSeekKeys(fFileSimple.fKeyOffset);
1431 RTFKeyList keyList{1};
1432 RTFKey keyKeyList(fFileSimple.fControlBlock->fFileRecord.GetSeekKeys(), RTFHeader::kBEGIN, strEmpty, strFileName,
1433 strEmpty, keyList.GetSize() + keyRNTuple.fKeyLen);
1434 fFileSimple.Write(&keyKeyList, keyKeyList.GetHeaderSize(), fFileSimple.fControlBlock->fFileRecord.GetSeekKeys());
1435 fFileSimple.Write(&strEmpty, strEmpty.GetSize());
1436 fFileSimple.Write(&strFileName, strFileName.GetSize());
1437 fFileSimple.Write(&strEmpty, strEmpty.GetSize());
1438 fFileSimple.Write(&keyList, keyList.GetSize());
1439 fFileSimple.Write(&keyRNTuple, keyRNTuple.GetHeaderSize());
1440 // Write class name, object name, and title for this key.
1441 fFileSimple.Write(&strRNTupleClass, strRNTupleClass.GetSize());
1442 fFileSimple.Write(&strRNTupleName, strRNTupleName.GetSize());
1443 fFileSimple.Write(&strEmpty, strEmpty.GetSize());
1444 fFileSimple.fControlBlock->fFileRecord.fNBytesKeys =
1445 fFileSimple.fFilePos - fFileSimple.fControlBlock->fFileRecord.GetSeekKeys();
1446 fFileSimple.fKeyOffset = fFileSimple.fFilePos;
1447}
1448
1450{
1451 fFileSimple.fControlBlock->fHeader.SetSeekFree(fFileSimple.fKeyOffset);
1452 RTFString strEmpty;
1453 RTFString strFileName{fFileName};
1454 RTFFreeEntry freeEntry;
1455 RTFKey keyFreeList(fFileSimple.fControlBlock->fHeader.GetSeekFree(), RTFHeader::kBEGIN, strEmpty, strFileName,
1456 strEmpty, freeEntry.GetSize());
1457 std::uint64_t firstFree = fFileSimple.fControlBlock->fHeader.GetSeekFree() + keyFreeList.GetSize();
1458 freeEntry.Set(firstFree, std::max(2000000000ULL, ((firstFree / 1000000000ULL) + 1) * 1000000000ULL));
1459 fFileSimple.WriteKey(&freeEntry, freeEntry.GetSize(), freeEntry.GetSize(),
1460 fFileSimple.fControlBlock->fHeader.GetSeekFree(), RTFHeader::kBEGIN, "", fFileName, "");
1461 fFileSimple.fControlBlock->fHeader.SetNbytesFree(fFileSimple.fFilePos -
1462 fFileSimple.fControlBlock->fHeader.GetSeekFree());
1463 fFileSimple.fControlBlock->fHeader.SetEnd(fFileSimple.fFilePos);
1464}
1465
1467{
1468 RTFString strRNTupleClass{"ROOT::RNTuple"};
1469 RTFString strRNTupleName{fNTupleName};
1470 RTFString strEmpty;
1471
1472 RTFNTuple ntupleOnDisk(fNTupleAnchor);
1473 RUInt64BE checksum{XXH3_64bits(ntupleOnDisk.GetPtrCkData(), ntupleOnDisk.GetSizeCkData())};
1474 fFileSimple.fControlBlock->fSeekNTuple = fFileSimple.fKeyOffset;
1475
1476 char keyBuf[RTFNTuple::GetSizePlusChecksum()];
1477
1478 // concatenate the RNTuple anchor with its checksum
1479 memcpy(keyBuf, &ntupleOnDisk, sizeof(RTFNTuple));
1480 memcpy(keyBuf + sizeof(RTFNTuple), &checksum, sizeof(checksum));
1481
1482 const auto sizeAnchor = sizeof(RTFNTuple) + sizeof(checksum);
1483 char zipAnchor[RTFNTuple::GetSizePlusChecksum()];
1485
1486 fFileSimple.WriteKey(zipAnchor, szZipAnchor, sizeof(keyBuf), fFileSimple.fControlBlock->fSeekNTuple,
1487 RTFHeader::kBEGIN, "ROOT::RNTuple", fNTupleName, "");
1488 return szZipAnchor;
1489}
1490
1492{
1493 RTFString strTFile{"TFile"};
1494 RTFString strFileName{fFileName};
1495 RTFString strEmpty;
1496
1497 fFileSimple.fControlBlock->fHeader = RTFHeader(defaultCompression);
1498
1499 RTFUUID uuid;
1500
1501 // First record of the file: the TFile object at offset kBEGIN (= 100)
1502 RTFKey keyRoot(RTFHeader::kBEGIN, 0, strTFile, strFileName, strEmpty,
1503 sizeof(RTFDirectory) + strFileName.GetSize() + strEmpty.GetSize() + uuid.GetSize());
1504 std::uint32_t nbytesName = keyRoot.fKeyLen + strFileName.GetSize() + 1;
1505 fFileSimple.fControlBlock->fFileRecord.fNBytesName = nbytesName;
1506 fFileSimple.fControlBlock->fHeader.SetNbytesName(nbytesName);
1507
1508 fFileSimple.Write(&keyRoot, keyRoot.GetHeaderSize(), RTFHeader::kBEGIN);
1509 // Write class name, object name, and title for the TFile key.
1510 fFileSimple.Write(&strTFile, strTFile.GetSize());
1511 fFileSimple.Write(&strFileName, strFileName.GetSize());
1512 fFileSimple.Write(&strEmpty, strEmpty.GetSize());
1513 // Write the name and title of the TNamed preceding the TFile entry.
1514 fFileSimple.Write(&strFileName, strFileName.GetSize());
1515 fFileSimple.Write(&strEmpty, strEmpty.GetSize());
1516 // Will be overwritten on commit
1517 fFileSimple.fControlBlock->fSeekFileRecord = fFileSimple.fFilePos;
1518 fFileSimple.Write(&fFileSimple.fControlBlock->fFileRecord, fFileSimple.fControlBlock->fFileRecord.GetSize());
1519 fFileSimple.Write(&uuid, uuid.GetSize());
1520
1521 // Padding bytes to allow the TFile record to grow for a big file
1522 RUInt32BE padding{0};
1523 for (int i = 0; i < 3; ++i)
1524 fFileSimple.Write(&padding, sizeof(padding));
1525 fFileSimple.fKeyOffset = fFileSimple.fFilePos;
1526}
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
Definition RError.hxx:299
static void PrepareBlobKey(std::int64_t offset, size_t nbytes, size_t len, unsigned char buffer[kBlobKeyLen])
Prepare a blob key in the provided buffer, which must provide space for kBlobKeyLen bytes.
static size_t ComputeNumChunks(size_t nbytes, size_t maxChunkSize)
static constexpr auto kBlobKeyLen
#define ROOT_VERSION_CODE
Definition RVersion.hxx:24
#define ClassDefInlineOverride(name, id)
Definition Rtypes.h:358
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
const Int_t kBEGIN
Definition TFile.cxx:183
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
char name[80]
Definition TGX11.cxx:110
Binding & operator=(OUT(*fun)(void))
void ReadBuffer(char *&buffer) override
T1 fFirst
Definition X11Events.mm:86
#define _(A, B)
Definition cfortran.h:108
The RKeyBlob writes an invisible key into a TFile.
void Reserve(size_t nbytes, std::uint64_t *seekKey)
Register a new key for a data record of size nbytes.
std::uint64_t SearchInDirectory(std::uint64_t &offsetDir, std::string_view keyName, std::string_view typeName)
Searches for a key with the given name and type in the key index of the directory starting at offsetD...
RResult< RNTuple > GetNTuple(std::string_view ntupleName)
Extracts header and footer location for the RNTuple identified by ntupleName.
void ReadBuffer(void *buffer, size_t nbytes, std::uint64_t offset)
Reads a given byte range from the file into the provided memory buffer.
RResult< RNTuple > GetNTupleBare(std::string_view ntupleName)
Used when the file container turns out to be a bare file.
RResult< RNTuple > GetNTupleProper(std::string_view ntuplePath)
Used when the file turns out to be a TFile container.
size_t Zip(const void *from, size_t nbytes, int compression, Writer_t fnWriter)
Returns the size of the compressed data.
Helper class to uncompress data blocks in the ROOT compression frame format.
Write RNTuple data blocks in a TFile or a bare file container.
RNTupleFileWriter(std::string_view name, std::uint64_t maxKeySize)
std::uint64_t WriteBlob(const void *data, size_t nbytes, size_t len)
Writes a new record as an RBlob key into the file.
std::uint64_t WriteNTupleFooter(const void *data, size_t nbytes, size_t lenFooter)
Writes the compressed footer and registeres its location; lenFooter is the size of the uncompressed f...
void UpdateStreamerInfos(const RNTupleSerializer::StreamerInfoMap_t &streamerInfos)
Ensures that the streamer info records passed as argument are written to the file.
void WriteTFileStreamerInfo(int compression)
Write the compressed streamer info record with the description of the RNTuple class.
RFileSimple fFileSimple
For simple use cases, survives without libRIO dependency.
void WriteTFileKeysList(std::uint64_t anchorSize)
Write the TList with the RNTuple key.
void WriteTFileFreeList()
Last record in the file.
EContainerFormat
For testing purposes, RNTuple data can be written into a bare file container instead of a ROOT file.
std::uint64_t ReserveBlob(size_t nbytes, size_t len, unsigned char keyBuffer[kBlobKeyLen]=nullptr)
Reserves a new record as an RBlob key in the file.
std::uint64_t WriteTFileNTupleKey(int compression)
The only key that will be visible in file->ls() Returns the size on disk of the anchor object.
static std::unique_ptr< RNTupleFileWriter > Recreate(std::string_view ntupleName, std::string_view path, EContainerFormat containerFormat, const RNTupleWriteOptions &options)
Create or truncate the local file given by path with the new empty RNTuple identified by ntupleName.
static std::unique_ptr< RNTupleFileWriter > Append(std::string_view ntupleName, TDirectory &fileOrDirectory, std::uint64_t maxKeySize)
The directory parameter can also be a TFile object (TFile inherits from TDirectory).
void Commit(int compression=RCompressionSetting::EDefaults::kUseGeneralPurpose)
Writes the RNTuple key to the file so that the header and footer keys can be found.
void WriteTFileSkeleton(int defaultCompression)
For a TFile container written by a C file stream, write the header and TFile object.
void WriteBareFileSkeleton(int defaultCompression)
For a bare file, which is necessarily written by a C file stream, write file header.
static constexpr std::size_t kBlobKeyLen
The key length of a blob. It is always a big key (version > 1000) with class name RBlob.
std::uint64_t WriteNTupleHeader(const void *data, size_t nbytes, size_t lenHeader)
Writes the compressed header and registeres its location; lenHeader is the size of the uncompressed h...
RNTuple fNTupleAnchor
Header and footer location of the ntuple, written on Commit()
void WriteIntoReservedBlob(const void *buffer, size_t nbytes, std::int64_t offset)
Write into a reserved record; the caller is responsible for making sure that the written byte range i...
RNTupleSerializer::StreamerInfoMap_t fStreamerInfoMap
Set of streamer info records that should be written to the file.
static std::uint32_t SerializeUInt64(std::uint64_t val, void *buffer)
std::map< Int_t, TVirtualStreamerInfo * > StreamerInfoMap_t
static std::uint32_t DeserializeUInt64(const void *buffer, std::uint64_t &val)
Common user-tunable settings for storing ntuples.
The RRawFile provides read-only access to local and remote files.
Definition RRawFile.hxx:43
Base class for all ROOT issued exceptions.
Definition RError.hxx:79
Representation of an RNTuple data set in a ROOT file.
Definition RNTuple.hxx:69
std::uint64_t fMaxKeySize
The maximum size for a TKey payload. Payloads bigger than this size will be written as multiple blobs...
Definition RNTuple.hxx:109
static TClass * Class()
The class is used as a return type for operations that can fail; wraps a value of type T or an RError...
Definition RError.hxx:197
The concrete implementation of TBuffer for writing/reading to/from a ROOT file or socket.
Definition TBufferFile.h:47
void TagStreamerInfo(TVirtualStreamerInfo *info) override
Mark the classindex of the current file as using this TStreamerInfo.
void SetParent(TObject *parent)
Set parent owning this buffer.
Definition TBuffer.cxx:270
@ kWrite
Definition TBuffer.h:73
void SetBufferOffset(Int_t offset=0)
Definition TBuffer.h:93
Int_t Length() const
Definition TBuffer.h:100
char * Buffer() const
Definition TBuffer.h:96
Describe directory structure in memory.
Definition TDirectory.h:45
A ROOT file is an on-disk file, usually with extension .root, that stores objects in a file-system-li...
Definition TFile.h:131
Bool_t IsBinary() const
Definition TFile.h:338
Book space in a file, create I/O buffers, to fill them, (un)compress them.
Definition TKey.h:28
Int_t Sizeof() const override
Return the size in bytes of the key header structure.
Definition TKey.cxx:1343
Int_t fVersion
Key version identifier.
Definition TKey.h:39
Int_t fLeft
Number of bytes left in current segment.
Definition TKey.h:48
Short_t fKeylen
Number of bytes for the key itself.
Definition TKey.h:43
Long64_t fSeekKey
Location of object on file.
Definition TKey.h:45
virtual void Create(Int_t nbytes, TFile *f=nullptr)
Create a TKey object of specified size.
Definition TKey.cxx:459
TString fClassName
Object Class name.
Definition TKey.h:47
A doubly linked list.
Definition TList.h:38
This class defines a UUID (Universally Unique IDentifier), also known as GUIDs (Globally Unique IDent...
Definition TUUID.h:42
void FillBuffer(char *&buffer)
Stream UUID into output buffer.
Definition TUUID.cxx:275
RNTuple CreateAnchor(std::uint16_t versionEpoch, std::uint16_t versionMajor, std::uint16_t versionMinor, std::uint16_t versionPatch, std::uint64_t seekHeader, std::uint64_t nbytesHeader, std::uint64_t lenHeader, std::uint64_t seekFooter, std::uint64_t nbytesFooter, std::uint64_t lenFooter, std::uint64_t maxKeySize)
Definition RNTuple.cxx:53
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
Helper templated class for swapping bytes; specializations for N={2,4,8} are provided below.
Definition Byteswap.h:124
void Write(const void *buffer, size_t nbytes, std::int64_t offset)
Low-level writing using a TFile.
std::uint64_t ReserveBlobKey(size_t nbytes, size_t len, unsigned char keyBuffer[kBlobKeyLen]=nullptr)
Reserves an RBlob opaque key as data record and returns the offset of the record.
std::unique_ptr< ROOT::Experimental::Internal::RTFileControlBlock > fControlBlock
Keeps track of TFile control structures, which need to be updated on committing the data set.
void Write(const void *buffer, size_t nbytes, std::int64_t offset=-1)
Writes bytes in the open stream, either at fFilePos or at the given offset.
std::uint64_t ReserveBlobKey(std::size_t nbytes, std::size_t len, unsigned char keyBuffer[kBlobKeyLen]=nullptr)
Reserves an RBlob opaque key as data record and returns the offset of the record.
std::uint64_t WriteKey(const void *buffer, std::size_t nbytes, std::size_t len, std::int64_t offset=-1, std::uint64_t directoryOffset=100, const std::string &className="", const std::string &objectName="", const std::string &title="")
Writes a TKey including the data record, given by buffer, into fFile; returns the file offset to the ...
If a TFile container is written by a C stream (simple file), on dataset commit, the file header and t...
auto * tt
Definition textangle.C:16