22 constexpr char MAGIC[8] = {
'G',
'a',
'u',
'd',
'i',
'N',
'T',
'B' };
23 constexpr std::uint32_t BYTE_ORDER_MARK = 0x01020304u;
24 constexpr std::uint32_t VERSION = 1;
25 constexpr std::uint32_t CODEC_NONE = 0;
26 constexpr std::uint32_t CODEC_ZSTD = 1;
27 constexpr std::size_t READ_BUFFER = 64 * 1024;
28 constexpr std::size_t MAX_RECORD = std::size_t( 1 ) << 30;
29 constexpr std::size_t MAX_BLOCK = MAX_RECORD;
30 constexpr std::size_t MAX_HEADER = 64 * 1024 * 1024;
32 constexpr std::uint64_t MAX_STORED = std::uint64_t( MAX_BLOCK ) + MAX_RECORD +
sizeof( std::uint32_t );
33 constexpr std::size_t PREAMBLE =
sizeof( MAGIC ) + 4 *
sizeof( std::uint32_t );
34 constexpr std::size_t BLOCK_HEADER = 2 *
sizeof( std::uint32_t );
36 void put32(
char* dest, std::uint32_t v ) { std::memcpy( dest, &v,
sizeof( v ) ); }
37 std::uint32_t get32(
const char* src ) {
39 std::memcpy( &v, src,
sizeof( v ) );
58 ( err ?
std::
string(
" (" ) +
std::strerror( err ) +
")" :
"" ) )
65 if ( header.size() > MAX_HEADER )
throw Error(
m_path,
"header too large" );
70 std::setvbuf(
m_file.get(),
nullptr, _IONBF, 0 );
71 std::vector<char> preamble( MAGIC, MAGIC +
sizeof( MAGIC ) );
72 char words[4 *
sizeof( std::uint32_t )];
73 put32( words, BYTE_ORDER_MARK );
74 put32( words + 4, VERSION );
75 put32( words + 8,
m_level ? CODEC_ZSTD : CODEC_NONE );
76 put32( words + 12,
static_cast<std::uint32_t
>( header.size() ) );
77 preamble.insert( preamble.end(), words, words +
sizeof( words ) );
78 preamble.insert( preamble.end(), header.begin(), header.end() );
79 write( preamble.data(), preamble.size() );
89 m_buf.resize(
m_buf.size() +
sizeof( std::uint32_t ) );
95 if (
bytes == 0 )
return;
98 const char* src =
static_cast<const char*
>( data );
120 if (
m_buf.empty() )
return;
128 const long start = std::ftell(
m_file.get() );
129 const std::uint64_t storedStart =
m_stored;
131 const char* data =
m_buf.data();
132 std::size_t stored =
m_buf.size();
133 std::vector<char> out;
136 out.resize( ZSTD_compressBound(
m_buf.size() ) );
137 stored = ZSTD_compress( out.data(), out.size(),
m_buf.data(),
m_buf.size(),
m_level );
138 if ( ZSTD_isError( stored ) )
139 throw Error(
m_path, std::string(
"zstd compression failed: " ) + ZSTD_getErrorName( stored ) );
143 char hdr[BLOCK_HEADER];
144 put32( hdr,
static_cast<std::uint32_t
>( stored ) );
145 put32( hdr + 4,
static_cast<std::uint32_t
>(
m_buf.size() ) );
146 write( hdr,
sizeof( hdr ) );
147 write( data, stored );
150 if ( start >= 0 && ::ftruncate( ::fileno(
m_file.get() ), start ) == 0 ) {
151 std::fseek(
m_file.get(), 0, SEEK_END );
166 bool ok = std::fflush(
m_file.get() ) == 0;
167 int err = ok ? 0 : errno;
169 if ( std::fclose(
m_file.release() ) != 0 && ok ) {
173 if ( !ok )
throw Error(
m_path,
"close failed", err );
180 std::setvbuf(
m_file.get(),
nullptr, _IOFBF, READ_BUFFER );
184 char preamble[PREAMBLE];
185 if (
read( preamble, PREAMBLE ) != PREAMBLE || std::memcmp( preamble, MAGIC,
sizeof( MAGIC ) ) != 0 )
186 throw Error(
m_path,
"not a Gaudi ntuple buffer file" );
187 const char* words = preamble +
sizeof( MAGIC );
188 if ( get32( words ) != BYTE_ORDER_MARK )
throw Error(
m_path,
"buffer file has foreign byte order" );
189 if ( get32( words + 4 ) != VERSION )
throw Error(
m_path,
"unsupported buffer file version" );
190 switch ( get32( words + 8 ) ) {
198 throw Error(
m_path,
"unknown buffer file compression" );
200 const std::uint32_t headerLen = get32( words + 12 );
201 if ( headerLen > MAX_HEADER )
throw Error(
m_path,
"corrupt header length" );
209 const std::size_t got = std::fread( dest, 1, bytes,
m_file.get() );
210 if ( got != bytes && std::ferror(
m_file.get() ) )
throw Error(
m_path,
"read failed", errno );
215 char hdr[BLOCK_HEADER];
216 const std::size_t got =
read( hdr,
sizeof( hdr ) );
217 if ( got == 0 )
return false;
218 if ( got !=
sizeof( hdr ) )
throw Error(
m_path,
"truncated block header" );
219 const std::uint32_t stored = get32( hdr ), raw = get32( hdr + 4 );
221 if ( stored > MAX_STORED || raw > MAX_STORED )
throw Error(
m_path,
"corrupt block header" );
222 const long pos = std::ftell(
m_file.get() );
223 if (
m_size > 0 && pos >= 0 && stored >
m_size -
static_cast<std::uintmax_t
>( pos ) )
227 if ( stored != raw )
throw Error(
m_path,
"corrupt block header" );
237 const unsigned long long content = ZSTD_getFrameContentSize(
m_stored.data(), stored );
238 if ( content != ZSTD_CONTENTSIZE_UNKNOWN && content != ZSTD_CONTENTSIZE_ERROR && content != raw )
241 const std::size_t n = ZSTD_decompress(
m_block.data(), raw,
m_stored.data(), stored );
242 if ( ZSTD_isError( n ) )
throw Error(
m_path, std::string(
"corrupt block: " ) + ZSTD_getErrorName( n ) );
243 if ( n != raw )
throw Error(
m_path,
"corrupt block: size mismatch" );
247 throw Error(
m_path,
"compressed buffer file but zstd is not available in this build" );
255 if (
m_block.size() -
m_pos <
sizeof( std::uint32_t ) )
throw Error(
m_path,
"corrupt block: bad record length" );
256 const std::uint32_t len = get32(
m_block.data() +
m_pos );
257 m_pos +=
sizeof( std::uint32_t );
259 record = std::span<const char>(
m_block.data() +
m_pos, len );
Any I/O or format problem, carrying the file it happened on.
const std::filesystem::path & path() const
std::filesystem::path m_path
Error(const std::filesystem::path &path, std::string_view what, int err=0)
std::size_t read(void *dest, std::size_t bytes)
std::vector< char > m_stored
Reader(std::filesystem::path path)
Open path and validate the preamble.
bool next(std::span< const char > &record)
Read the next record; false at a clean end of file. A partial block throws.
const std::filesystem::path & path() const
std::vector< char > m_block
std::filesystem::path m_path
void write(const void *data, std::size_t bytes)
void append(const void *data, std::size_t bytes)
std::uint64_t m_pendingBytes
std::size_t m_recordStart
const std::filesystem::path & path() const
std::filesystem::path m_path
std::vector< char > m_buf
std::size_t blockSize() const
void close()
Flush and close; throws on failure. Safe to call twice.
std::uint64_t bytes() const
Payload bytes of those records.
Append-only file of length-prefixed records, used to park ntuple rows on disk during the event loop a...
bool zstdAvailable()
Whether this build can write and read compressed blocks.
std::size_t maxBlockSize()
Largest block size a Writer accepts.