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

メモリ中のバッファ・キャッシュに残っている上書き内容をジャーナリング・ファイルへ吐き出す journaling メンバ関数の機能に加えて、 それに続いてジャーナリング・ファイルへの記録をスナップショット・ファイルへ反映するメンバ関数もあって、 data_flush と名づけています。 このメンバ関数は、 スナップショットの更新に成功すると、 ジャーナリング・ファイルを空にし、 巻き戻し時点をこの更新直後に設定しなおします。

bool
ioblock_type::data_flush (void)
{
    if (m_mode == "r" || m_fail) {
        return false;
    }
    if (! (journaling () && redo ())) {
        return false;
    }
    lseek (m_log, 0, SEEK_SET);
    ftruncate (m_log, 0);
    fsync (m_log);
    m_save_point = 0;
    m_head.clear ();
    return true;
}

逆に、 現在の更新内容を廃棄して、 data_opendata_flush のいずれか最近の方に戻すには undo メンバ関数を使います。

bool
ioblock_type::undo (void)
{
    if (m_mode == "r") {
        return false;
    }
    lseek (m_log, m_save_point, SEEK_SET);
    ftruncate (m_log, m_save_point);
    fsync (m_log);
    cache_clear ();
    reload_head ();
    m_file_size = m_orig_size;
    m_fail = false;
    return true;
}

undo をおこなうとファイルの内容とキャッシュしている内容がずれてしまうため、 cache_clear でキャッシュを空に戻します。

void
ioblock_type::cache_clear (void)
{
    dirty_clear ();
    if (m_ring->m_next != m_free) {
        relink (m_ring->m_prev, m_ring->m_next, m_ring->m_prev, m_ring->m_next);
        relink (m_free->m_prev, m_ring, m_free->m_prev, m_ring);
        relink (m_ring, m_free, m_ring, m_free);
    }
}

これら 2 つの対称の処理をするメンバ関数data_close が自動的に実行を試みるようにしています。 さらに、 この入出力クラスのデストラクタが data_close をおこないます。

void
ioblock_type::data_close (void)
{
    if (m_log >= 0 && m_mode != "r") {
        if (m_fail) {
            undo ();
        }
        else {
            data_flush ();
        }
    }
    if (m_desc >= 0) {
        close (m_desc);
    }
    if (m_log >= 0) {
        close (m_log);
    }
    if (m_mode != "r" && m_save_point == 0) {
        unlink (m_log_name.c_str ());
    }
    m_desc = -1;
    m_log = -1;
    dirty_clear ();
    dispose_ring ();
}

ioblock_type::~ioblock_type ()
{
    data_close ();
}

バッファ・キャッシュをジャーナリング・ファイルへ書き出す journaling メンバ関数は「その2」に書いてあります。 残りの処理のジャーナリング・ファイルをスナップショットへ反映するのは redo メンバ関数です。 このメンバ関数は、 reload_head で、 ジャーナリング・ファイル中のバッファがスナップショットのどこに対応するかを表す m_head を念のために作り直してから、 ブロックをスナップショット・ファイルのオフセットの昇順にすべて書き込みます。 全部を書き込んだら、 スナップショット・ファイルのサイズを ftruncate システム・コールで更新して、 それをスナップショット・ファイルのファイル・サイズである m_orig_size にします。 なお、 スナップショットの元のサイズよりも大きなオフセットのブロックが連続していない場合は、 穴空きファイルになることがあります。 そのことを警告したり防止するようなことはしません。

bool
ioblock_type::redo (void)
{
    if (! reload_head ()) {
        return set_fail ();
    }
    std::string buf (m_block_size, 0);
    for (auto it : m_head) {
        if (pread (m_log, &(buf[0]), m_block_size, it.second) != m_block_size) {
            return false;
        }
        if (pwrite (m_desc, &(buf[0]), m_block_size, it.first) != m_block_size) {
            return false;
        }
    }
    if (lseek (m_desc, m_file_size, SEEK_SET) == (off_t)(-1)) {
        return false;
    }
    ftruncate (m_desc, m_file_size);
    fsync (m_desc);
    m_orig_size = m_file_size;
    return true;
}

m_head の作り直しをおこなう reload_head は、 ジャーナリング・ファイルへ追記する block_overflow と共通のコードを使って逆の処理をします。 ファイルの先頭から、 ログを順番に辿っていきます。 ログのヘッダ部のブロックのオフセット表を読み込んで、 ブロックの内容部分は lseek システム・コールで読み飛ばします。 これを全部のログに対して繰り返して、 オフセット表を作り直します。 前回、 書くのを忘れていた、 ログごとに先頭に書き込む 4 オクテットのマジックと、 バッファからオフセットを読み取るためのポインタの共用体もここに書いておきます。

static char const JOURNAL_MAGIC[] = "jnal";
static std::size_t const JOURNAL_MAGIC_SIZE = 4U;

union pack_pointer_type {
    char* t;
    char const* s;
    off_t* off;
};

bool
ioblock_type::reload_head (void)
{
    std::map<off_t,off_t> head;
    if (lseek (m_log, 0, SEEK_SET) == (off_t)(-1)) {
        return set_fail ();
    }
    pack_pointer_type pack;
    off_t offset = 0;
    std::size_t block_size = 0;
    std::string buf (m_block_size, 0);
    while (read (m_log, &buf[0], m_block_size) == m_block_size) {
        if (buf.compare (0, JOURNAL_MAGIC_SIZE, JOURNAL_MAGIC) != 0) {
            return set_fail ();
        }
        pack.t = &(buf[JOURNAL_MAGIC_SIZE]);
        m_file_size = *pack.off++;
        block_size = *pack.off++;
        std::size_t count = *pack.off++;
        if (block_size != m_block_size) {
            return set_fail ();
        }
        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);
        if (buf_size > m_block_size) {
            buf.resize (buf_size, 0);
            std::size_t const rdcount = buf_size - m_block_size;
            if (read (m_log, &buf[m_block_size], rdcount) != rdcount) {
                return set_fail ();
            }
        }
        pack.t = &(buf[hdsize]);
        offset += buf_size;
        while (count-- > 0) {
            head[*pack.off++] = offset;
            offset += m_block_size;
        }
        if (lseek (m_log, offset, SEEK_SET) == (off_t)(-1)) {
            return set_fail ();
        }
        buf.resize (m_block_size, 0);
    }
    std::swap (m_head, head);
    return true;
}