+/**@brief gain access to it before making any modications.
+ * @param journal current journal session
+ * @param trans transaction
+ * @param block descriptor
+ * @return standard error code.*/
+int jbd_trans_get_access(struct jbd_journal *journal,
+ struct jbd_trans *trans,
+ struct ext4_block *block)
+{
+ int r = EOK;
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ struct jbd_buf *jbd_buf = block->buf->end_write_arg;
+
+ /* If the buffer has already been modified, we should
+ * flush dirty data in this buffer to disk.*/
+ if (ext4_bcache_test_flag(block->buf, BC_DIRTY) &&
+ block->buf->end_write == jbd_trans_end_write) {
+ ext4_assert(jbd_buf);
+ if (jbd_buf->trans != trans)
+ r = ext4_block_flush_buf(fs->bdev, block->buf);
+
+ }
+ return r;
+}
+
+static struct jbd_block_rec *
+jbd_trans_block_rec_lookup(struct jbd_journal *journal,
+ ext4_fsblk_t lba)
+{
+ struct jbd_block_rec tmp = {
+ .lba = lba
+ };
+
+ return RB_FIND(jbd_block,
+ &journal->block_rec_root,
+ &tmp);
+}
+
+static void
+jbd_trans_change_ownership(struct jbd_block_rec *block_rec,
+ struct jbd_trans *new_trans,
+ struct ext4_buf *new_buf)
+{
+ LIST_REMOVE(block_rec, tbrec_node);
+ /* Now this block record belongs to this transaction. */
+ LIST_INSERT_HEAD(&new_trans->tbrec_list, block_rec, tbrec_node);
+ block_rec->trans = new_trans;
+ block_rec->buf = new_buf;
+}
+
+static inline struct jbd_block_rec *
+jbd_trans_insert_block_rec(struct jbd_trans *trans,
+ ext4_fsblk_t lba,
+ struct ext4_buf *buf)
+{
+ struct jbd_block_rec *block_rec;
+ block_rec = jbd_trans_block_rec_lookup(trans->journal, lba);
+ if (block_rec) {
+ jbd_trans_change_ownership(block_rec, trans, buf);
+ return block_rec;
+ }
+ block_rec = calloc(1, sizeof(struct jbd_block_rec));
+ if (!block_rec)
+ return NULL;
+
+ block_rec->lba = lba;
+ block_rec->buf = buf;
+ block_rec->trans = trans;
+ TAILQ_INIT(&block_rec->dirty_buf_queue);
+ LIST_INSERT_HEAD(&trans->tbrec_list, block_rec, tbrec_node);
+ RB_INSERT(jbd_block, &trans->journal->block_rec_root, block_rec);
+ return block_rec;
+}
+
+static void
+jbd_trans_finish_callback(struct jbd_journal *journal,
+ const struct jbd_trans *trans,
+ struct jbd_block_rec *block_rec,
+ bool abort)
+{
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ if (block_rec->trans != trans)
+ return;
+
+ if (!abort) {
+ struct jbd_buf *jbd_buf, *tmp;
+ TAILQ_FOREACH_SAFE(jbd_buf,
+ &block_rec->dirty_buf_queue,
+ dirty_buf_node,
+ tmp) {
+ /* All we need is a fake ext4_buf. */
+ struct ext4_buf buf;
+
+ jbd_trans_end_write(fs->bdev->bc,
+ &buf,
+ EOK,
+ jbd_buf);
+ }
+ } else {
+ struct jbd_buf *jbd_buf;
+ struct ext4_block jbd_block = EXT4_BLOCK_ZERO(),
+ block = EXT4_BLOCK_ZERO();
+ jbd_buf = TAILQ_LAST(&block_rec->dirty_buf_queue,
+ jbd_buf_dirty);
+ if (jbd_buf) {
+ ext4_assert(ext4_block_get(fs->bdev,
+ &jbd_block,
+ jbd_buf->jbd_lba) == EOK);
+ ext4_assert(ext4_block_get_noread(fs->bdev,
+ &block,
+ block_rec->lba) == EOK);
+ memcpy(block.data, jbd_block.data,
+ journal->block_size);
+
+ jbd_trans_change_ownership(block_rec,
+ jbd_buf->trans, block.buf);
+
+ block.buf->end_write = jbd_trans_end_write;
+ block.buf->end_write_arg = jbd_buf;
+
+ ext4_bcache_set_flag(jbd_block.buf, BC_TMP);
+ ext4_bcache_set_dirty(block.buf);
+
+ ext4_block_set(fs->bdev, &jbd_block);
+ ext4_block_set(fs->bdev, &block);
+ return;
+ }
+ }
+}
+
+static inline void
+jbd_trans_remove_block_rec(struct jbd_journal *journal,
+ struct jbd_block_rec *block_rec,
+ struct jbd_trans *trans)
+{
+ /* If this block record doesn't belong to this transaction,
+ * give up.*/
+ if (block_rec->trans == trans) {
+ LIST_REMOVE(block_rec, tbrec_node);
+ RB_REMOVE(jbd_block,
+ &journal->block_rec_root,
+ block_rec);
+ free(block_rec);
+ }
+}
+
+/**@brief Add block to a transaction and mark it dirty.
+ * @param trans transaction
+ * @param block block descriptor
+ * @return standard error code*/
+int jbd_trans_set_block_dirty(struct jbd_trans *trans,
+ struct ext4_block *block)