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.

DescriptionSize
Root block header - static part1k
Root block header - dynamic part1k
1st root block division31k
2nd root block division31k
Total64k

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;
};