ジャーナリング付き入出力クラス (その2)

ファイルのオフセットを指定して、 そこから指定バイト数を読み出す data_read メンバ関数は、 読み出し範囲が含まれるブロックのキャッシュを調べて、 キャッシュされていないときは、 block_underflow メンバ関数で内容を読み取ります。 読み取った内容から読み出し範囲を切り出してバッファへ書き込み、 複数のブロックへまたがっているときは、 次のブロック以降も同じように扱っていきます。

bool
ioblock_type::data_read (std::string& data, std::size_t count, off_t offset)
{
    data.clear ();
// ここから 2017-01-20 追加
    if (offset >= m_file_size) {
        return false;
    }
    if (offset + count > m_file_size) {
        count = m_file_size - offset;
    }
// ここまで 2017-01-20 追加
    while (count > 0) {
        std::size_t const disp = offset & (m_block_size - 1U);
        off_t const block_offset = offset - disp;
        std::size_t const c = std::min (count, m_block_size - disp);
        std::string& buf = get_block (block_offset);
        if (buf.empty ()) {
            buf.resize (m_block_size, 0);
            if (! block_underflow (buf, block_offset)) {
                return set_fail ();
            }
        }
        std::string::const_iterator src = buf.cbegin () + disp;
        data.insert (data.end (), src, src + c);
        offset += c;
        count -= c;
    }
    return count == 0;
}

書き込みを行う data_write メンバ関数も同じようなやりかたでバッファからキャッシュへ書き込んでいきます。 ただし、 ブロック全体へ書き込むときは読み取る必要がないので、 そのときは block_underflow メンバ関数呼び出しをスキップします。 ブロックへ書き込んだら、 その都度、 paint_dirty メンバ関数で上書き済み印をつけて m_dirty 表へブロック・オブジェクトをつないでいきます。 さらに、 書き込みが終わった段階で、 m_dirty 表の大きさが制限を越えていないかチェックし、 越えているときは journaling メンバ関数ジャーナリング・ファイルへログ先行書き込みをおこないます。

bool
ioblock_type::data_write (std::string const& data, off_t offset)
{
    if (m_mode == "r" || m_fail) {
        return false;
    }
    std::string::const_iterator src = data.cbegin ();
    std::size_t count = data.size ();
    while (count > 0) {
        std::size_t const disp = offset & (m_block_size - 1U);
        off_t const block_offset = offset - disp;
        std::size_t const c = std::min (count, m_block_size - disp);
        std::string& buf = get_block (block_offset);
        if (buf.empty ()) {
            buf.resize (m_block_size, 0);
            if (c < m_block_size && ! block_underflow (buf, block_offset)) {
                return set_fail ();
            }
        }
        std::string::iterator dst = buf.begin () + disp;
        buf.replace (dst, dst + c, src, src + c);
        paint_dirty (block_offset);
        src += c;
        offset += c;
        count -= c;
    }
    if (m_file_size < offset) {
        m_file_size = offset;
    }
    if (m_dirty.size () > m_dirty_limit / m_block_size) {
        journaling ();
    }
    return true;
}

キャッシュへ内容を読み込む block_underflow メンバ関数は、 ブロックがジャーナリング・ファイルに記録済みのときはそちらから読み取り、 そうでないときはスナップショット・ファイルから内容を読み取ります。 ジャーナリング・ファイルには必ずブロック単位のバイトサイズで記録してありますが、 スナップショットは末尾ブロックの大きさがブロックサイズ未満になっているときがあるので、 そのときは必要な分だけを読み込みます。

bool
ioblock_type::block_underflow (std::string& buf, off_t offset)
{
    if (m_head.count (offset) > 0) {
        offset = m_head[offset];
        return pread (m_log, &buf[0], m_block_size, offset) == m_block_size;
    }
    else if (offset < m_orig_size) {
        std::size_t count = m_orig_size - offset;
        if (count > m_block_size) {
            count = m_block_size;
        }
        return pread (m_desc, &buf[0], count, offset) == count;
    }
    return true;
}

m_head メンバは、 ジャーナリング・ファイルへのブロックの書き込み場所の対応表で、 data_open 時に読み込んだ後、 journaling メンバ関数実行時に更新していきます。

journaling メンバ関数は、 ログ先行書き込み中にエラーが発生したとき、 ジャーナリング・ファイルを巻き戻す働きをします。

bool
ioblock_type::journaling (void)
{
    if (m_dirty.empty ()) {
        return true;
    }
    off_t const recover_offset = lseek (m_log, 0, SEEK_END);
    if (recover_offset == (off_t)(-1)) {
        return set_fail ();
    }
    bool const ok = block_overflow (recover_offset);
    if (! ok) {
        ftruncate (m_log, recover_offset);
        set_fail ();
    }
    fsync (m_log);
    return ok;
}

実際のジャーナリング・ファイルへのログの追記は、 block_overflow メンバ関数がおこないます。 ログには、 ファイル・サイズ、 ブロック・サイズ、 ログ中のブロックの個数を並べたヘッダに続いて、 ブロックの内容のコピーをオフセット順で並べていきます。 一つのジャーナリング・ファイルには、 複数のログが並んでいて、 ログが前のログのブロックの変更内容を上書きすることもあります。 m_head 表に、 上書きされていく最新のブロックの格納場所を記録しておき、 block_underflow で最新のブロックを読み込めるようにします。

なお、 次回に定義を記すpack_pointer_type はポインタの型変換をおこなう共用体です。 いちいち reinterpret_cast すると可読性が悪くなるので、 共用体でごまかしています。

bool
ioblock_type::block_overflow (off_t offset)
{
    std::map<off_t,off_t> head (m_head);
    std::size_t const count = m_dirty.size ();
    std::size_t const hdsize = JOURNAL_MAGIC_SIZE + 3U * sizeof (off_t);
    std::size_t buf_size = hdsize + count * sizeof (off_t);
    buf_size = (buf_size + m_block_size - 1U) & ~(m_block_size - 1U);
    std::string buf (buf_size, 0);
    buf.replace (0, JOURNAL_MAGIC_SIZE, JOURNAL_MAGIC);
    pack_pointer_type pack;
    pack.t = &buf[JOURNAL_MAGIC_SIZE];
    *pack.off++ = m_file_size;
    *pack.off++ = m_block_size;
    *pack.off++ = count;
    for (auto x : m_dirty) {
        *pack.off++ = x.second->m_offset;
    }
    if (write (m_log, &buf[0], buf.size ()) != buf.size ()) {
        return set_fail ();
    }
    offset += buf.size ();
    for (auto x : m_dirty) {
        head[x.second->m_offset] = offset;
        if (write (m_log, &(x.second->m_data[0]), m_block_size) != m_block_size) {
            return set_fail ();
        }
        offset += m_block_size;
    }
    dirty_clear ();
    std::swap (m_head, head);
    return true;
}