38 RootBlock
When the RAS uses unbuffered or direct file I/O, the operating system or filesystem may require file offsets, transfer lengths, and memory-buffer addresses to satisfy storage-specific alignment constraints. These constraints should be queried for the file or volume rather than inferred from a fixed historical sector size. Modern storage can require 4 kB alignment.
The current cxLss file format places the static and dynamic root-block headers on 1 kB boundaries. The current file RAS also reports a hard-coded 512-byte sector size rather than querying the actual direct-I/O alignment requirements. The 1 kB layout therefore reflects a historical format assumption; it does not by itself support an environment that requires alignment greater than 1 kB.
The total size of the root block is 64k bytes. It consists of the following sections.
| Description | Size |
|---|---|
| Root block header - static part | 1k |
| Root block header - dynamic part | 1k |
| 1st root block division | 31k |
| 2nd root block division | 31k |
| Total | 64k |
The static part of the root block stores the following information
- The magic string "CEDANET-LSS" used to verify that the file is in fact a Ceda LSS
- The total size of the root block header (currently 2k = 2048 bytes)
- The total size of the root block (currently 64k = 65536 bytes)
- The segment size (currently 512k = 524288 bytes)
- The disk sector size (typically 512 bytes)
The remainder is padded with zeros.
The dynamic part of the header starts on a new 1k boundary so it can be written independently of the static part. It contains a single boolean flag - the Graceful Shutdown Flag (GSF) used during startup to test whether the LSS was shut down gracefully the last time it was opened.
The root block divisions contain the check point information and are written in strict alternation using Challis' algorithm and validated with a 32bit CRC. The divisions start and end on 1k boundaries to ensure they can be written independently.
A root block division contains the following fields
|
Data type |
Description |
|
int m_crc |
32 bit CRC to validate the division |
|
int m_msn1 |
For Challis’ algorithm |
|
SUT m_sut |
A snapshot of the SUT at the time of the last check point. |
|
RPM m_rpm |
A snapshot of the root RPM-node at the time of the last check point |
|
LogRecordPosition m_positionOfLastCheckPoint |
Identifies the location of the last check point record in the log |
|
int m_currentOSID |
|
|
GUID m_checkPointId |
Each check point is given a unique identity by creating a guid at the time of the check point. This is used to validate flush units during a recovery scan. |
|
int m_msn2 |
For Challis’ algorithm |
Unbuffered or direct I/O can require aligned file offsets, transfer lengths, and memory buffers. The required alignment depends on the file, filesystem, volume, and storage device, and should be queried from the operating system. The 1 kB boundaries used by this root-block layout are a historical file-format choice, not a general statement about modern storage-sector sizes.
The total size of the root block is 64k bytes. It consists of the following sections
Description Size
----------------------------------------------
root block header
static part 1k
dynamic part 1k
1st root block division 31k
2nd root block division 31k
----------------------------------------------
total 64k
The static part of the root block stores the following information
- The magic string "CEDANET-LSS" used to verify that the file is in fact a Ceda LSS
- The total size of the root block header (currently 2k = 2048 bytes)
- The total size of the root block (currently 64k = 65536 bytes)
- The segment size (typically 512k = 524288 bytes)
- The disk sector size (typically 512 bytes)
The remainder is padded with zeros.
The dynamic part of the header starts on a new 1k boundary so it can be written independently of the static part. It contains a single boolean flag - the Graceful Shutdown Flag (GSF) used during startup to test whether the LSS was shut down gracefully the last time it was opened.
The root block divisions contain the check point information and are written in strict alternation using Challis' algorithm and validated with a 32bit CRC. The divisions start and end on 1k boundaries to ensure they can be written independently.
Code
struct RootBlockHeader
{
void Init(int diskSectorSize, int segmentSize);
void Validate(int diskSectorSize);
void WriteDiagnosticInfo(xostream& os) const;
static constexpr size_t SizeOnDisk() { return StaticSize() + DynamicSize(); }
static constexpr size_t StaticSize() { return ROOT_BLOCK_STATIC_HEADER_SIZE; }
static constexpr size_t StaticUsed() { return sizeof(magicStr_) + sizeof(schema_) + sizeof(rootHeaderSize_) + sizeof(rootBlockSize_) + sizeof(segmentSize_) + sizeof(diskSectorSize_); }
static constexpr size_t StaticPadding() { return StaticSize() - StaticUsed(); }
char magicStr_[LSS_MAGIC_STR_SIZE];
int32 schema_;
int32 rootHeaderSize_;
int32 rootBlockSize_; // Total size of the root block including the header
int32 segmentSize_;
int32 diskSectorSize_;
static constexpr size_t DynamicSize() { return ROOT_BLOCK_DYNAMIC_HEADER_SIZE; }
static constexpr size_t DynamicUsed() { return sizeof(gracefulShutdown_); }
static constexpr size_t DynamicPadding() { return DynamicSize() - DynamicUsed(); }
bool gracefulShutdown_;
};
/*
The root block consists of a header and two divisions. The divisions are written in strict
alternation using Challis' algorithm.
Each division needs to store the following information (see LSS::SerialiseRootBlockDivisionPayload())
Size
first magic string 14 bytes
schema 4 bytes
sut_ up to 28k bytes will allow for 448 terra byte stores
assuming 512k segments.
rpm_ up to about 2K bytes
positionOfLastCheckPoint_ 8 bytes
currentOidHigh_ 4 bytes
checkPointId_ 16 bytes
second magic string 12 bytes
*/
const int ROOT_BLOCK_DIVISION_SIZE = 31 * 1024;
// A division has [ crc, msn1, payload, msn2 ]
// The space available for the payload is 31732
const int ROOT_BLOCK_DIVISION_PAYLOAD_SIZE = ROOT_BLOCK_DIVISION_SIZE - sizeof(CRC32) - 2*sizeof(ModSeqNum);
// division must be 0 or 1.
// Returns the offset to the start of the division from the start of the root block - i.e. this is the
// offset to the division within the file.
inline ssize_t GetRootBlockDivisionOffset(int division)
{
cxAssert(division == 0 || division == 1);
return ROOT_BLOCK_HEADER_SIZE + division * ROOT_BLOCK_DIVISION_SIZE;
}
class RootBlock
{
public:
RootBlock(LSS& lss);
~RootBlock();
void Read(bool& gracefulShutdown);
void WriteStaticHeader();
void WriteDynamicHeader(bool gsf);
void PrepareNextDivisionInMemory(IInputStream& serialisedRpmRootNode);
void WriteNextDivisionToDisk();
void WriteDiagnosticInfo(xostream& os) const;
private:
LSS& lss_;
RootBlockHeader header_;
octet_t* rootBlockBuffer_ = nullptr;
ModSeqNum msn_ = 0;
};