+
+static void jbd_journal_write_sb(struct jbd_journal *journal)
+{
+ struct jbd_fs *jbd_fs = journal->jbd_fs;
+ jbd_set32(&jbd_fs->sb, start, journal->start);
+ jbd_set32(&jbd_fs->sb, sequence, journal->trans_id);
+ jbd_fs->dirty = true;
+}
+
+/**@brief Start accessing the journal.
+ * @param jbd_fs jbd filesystem
+ * @param journal current journal session
+ * @return standard error code*/
+int jbd_journal_start(struct jbd_fs *jbd_fs,
+ struct jbd_journal *journal)
+{
+ int r;
+ uint32_t features_incompatible =
+ ext4_get32(&jbd_fs->inode_ref.fs->sb,
+ features_incompatible);
+ struct ext4_block block = EXT4_BLOCK_ZERO();
+ features_incompatible |= EXT4_FINCOM_RECOVER;
+ ext4_set32(&jbd_fs->inode_ref.fs->sb,
+ features_incompatible,
+ features_incompatible);
+ r = ext4_sb_write(jbd_fs->inode_ref.fs->bdev,
+ &jbd_fs->inode_ref.fs->sb);
+ if (r != EOK)
+ return r;
+
+ journal->first = jbd_get32(&jbd_fs->sb, first);
+ journal->start = journal->first;
+ journal->last = journal->first;
+ journal->trans_id = 1;
+ journal->alloc_trans_id = 1;
+
+ journal->block_size = jbd_get32(&jbd_fs->sb, blocksize);
+
+ r = jbd_block_get_noread(jbd_fs,
+ &block,
+ journal->start);
+ if (r != EOK) {
+ memset(journal, 0, sizeof(struct jbd_journal));
+ return r;
+ }
+ memset(block.data, 0, journal->block_size);
+ ext4_bcache_set_dirty(block.buf);
+ r = jbd_block_set(jbd_fs, &block);
+ if (r != EOK) {
+ memset(journal, 0, sizeof(struct jbd_journal));
+ return r;
+ }
+
+ TAILQ_INIT(&journal->trans_queue);
+ TAILQ_INIT(&journal->cp_queue);
+ RB_INIT(&journal->block_rec_root);
+ journal->jbd_fs = jbd_fs;
+ jbd_journal_write_sb(journal);
+ return jbd_write_sb(jbd_fs);
+}
+
+static void jbd_journal_flush_trans(struct jbd_trans *trans)
+{
+ struct jbd_buf *jbd_buf, *tmp;
+ struct jbd_journal *journal = trans->journal;
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node,
+ tmp) {
+ struct ext4_buf *buf = jbd_buf->block_rec->buf;
+ if (buf)
+ ext4_block_flush_buf(fs->bdev, buf);
+ }
+}
+
+static void
+jbd_journal_skip_pure_revoke(struct jbd_journal *journal,
+ struct jbd_trans *trans)
+{
+ journal->start = trans->start_iblock +
+ trans->alloc_blocks;
+ wrap(&journal->jbd_fs->sb, journal->start);
+ journal->trans_id = trans->trans_id + 1;
+ jbd_journal_free_trans(journal,
+ trans, false);
+ jbd_journal_write_sb(journal);
+}
+
+static void
+jbd_journal_purge_cp_trans(struct jbd_journal *journal,
+ bool flush)
+{
+ struct jbd_trans *trans;
+ while ((trans = TAILQ_FIRST(&journal->cp_queue))) {
+ if (!trans->data_cnt) {
+ TAILQ_REMOVE(&journal->cp_queue,
+ trans,
+ trans_node);
+ jbd_journal_skip_pure_revoke(journal, trans);
+ } else {
+ if (trans->data_cnt ==
+ trans->written_cnt) {
+ journal->start =
+ trans->start_iblock +
+ trans->alloc_blocks;
+ wrap(&journal->jbd_fs->sb,
+ journal->start);
+ journal->trans_id =
+ trans->trans_id + 1;
+ TAILQ_REMOVE(&journal->cp_queue,
+ trans,
+ trans_node);
+ jbd_journal_free_trans(journal,
+ trans,
+ false);
+ jbd_journal_write_sb(journal);
+ } else if (!flush) {
+ journal->start =
+ trans->start_iblock;
+ wrap(&journal->jbd_fs->sb,
+ journal->start);
+ journal->trans_id =
+ trans->trans_id;
+ jbd_journal_write_sb(journal);
+ break;
+ } else
+ jbd_journal_flush_trans(trans);
+ }
+ }
+}
+
+/**@brief Stop accessing the journal.
+ * @param journal current journal session
+ * @return standard error code*/
+int jbd_journal_stop(struct jbd_journal *journal)
+{
+ int r;
+ struct jbd_fs *jbd_fs = journal->jbd_fs;
+ uint32_t features_incompatible;
+
+ /* Commit all the transactions to the journal.*/
+ jbd_journal_commit_all(journal);
+
+ /* Make sure that journalled content have reached
+ * the disk.*/
+ jbd_journal_purge_cp_trans(journal, true);
+
+ /* There should be no block record in this journal
+ * session. */
+ if (!RB_EMPTY(&journal->block_rec_root))
+ ext4_dbg(DEBUG_JBD,
+ DBG_WARN "There are still block records "
+ "in this journal session!\n");
+
+ features_incompatible =
+ ext4_get32(&jbd_fs->inode_ref.fs->sb,
+ features_incompatible);
+ features_incompatible &= ~EXT4_FINCOM_RECOVER;
+ ext4_set32(&jbd_fs->inode_ref.fs->sb,
+ features_incompatible,
+ features_incompatible);
+ r = ext4_sb_write(jbd_fs->inode_ref.fs->bdev,
+ &jbd_fs->inode_ref.fs->sb);
+ if (r != EOK)
+ return r;
+
+ journal->start = 0;
+ journal->trans_id = 0;
+ jbd_journal_write_sb(journal);
+ return jbd_write_sb(journal->jbd_fs);
+}
+
+/**@brief Allocate a block in the journal.
+ * @param journal current journal session
+ * @param trans transaction
+ * @return allocated block address*/
+static uint32_t jbd_journal_alloc_block(struct jbd_journal *journal,
+ struct jbd_trans *trans)
+{
+ uint32_t start_block;
+
+ start_block = journal->last++;
+ trans->alloc_blocks++;
+ wrap(&journal->jbd_fs->sb, journal->last);
+
+ /* If there is no space left, flush all journalled
+ * blocks to disk first.*/
+ if (journal->last == journal->start)
+ jbd_journal_purge_cp_trans(journal, true);
+
+ return start_block;
+}
+
+/**@brief Allocate a new transaction
+ * @param journal current journal session
+ * @return transaction allocated*/
+struct jbd_trans *
+jbd_journal_new_trans(struct jbd_journal *journal)
+{
+ struct jbd_trans *trans = calloc(1, sizeof(struct jbd_trans));
+ if (!trans)
+ return NULL;
+
+ /* We will assign a trans_id to this transaction,
+ * once it has been committed.*/
+ trans->journal = journal;
+ trans->data_csum = EXT4_CRC32_INIT;
+ trans->error = EOK;
+ TAILQ_INIT(&trans->buf_queue);
+ return trans;
+}
+
+static void jbd_trans_end_write(struct ext4_bcache *bc __unused,
+ struct ext4_buf *buf __unused,
+ int res,
+ void *arg);
+
+/**@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)
+{
+ struct jbd_buf *buf;
+
+ struct jbd_block_rec *block_rec;
+ if (block->buf->end_write == jbd_trans_end_write) {
+ buf = block->buf->end_write_arg;
+ if (buf && buf->trans == trans)
+ return EOK;
+ }
+ buf = calloc(1, sizeof(struct jbd_buf));
+ if (!buf)
+ return ENOMEM;
+
+ if ((block_rec = jbd_trans_insert_block_rec(trans,
+ block->lb_id,
+ block->buf)) == NULL) {
+ free(buf);
+ return ENOMEM;
+ }
+
+ TAILQ_INSERT_TAIL(&block_rec->dirty_buf_queue,
+ buf,
+ dirty_buf_node);
+
+ buf->block_rec = block_rec;
+ buf->trans = trans;
+ buf->block = *block;
+ ext4_bcache_inc_ref(block->buf);
+
+ /* If the content reach the disk, notify us
+ * so that we may do a checkpoint. */
+ block->buf->end_write = jbd_trans_end_write;
+ block->buf->end_write_arg = buf;
+
+ trans->data_cnt++;
+ TAILQ_INSERT_HEAD(&trans->buf_queue, buf, buf_node);
+
+ ext4_bcache_set_dirty(block->buf);
+ return EOK;
+}
+
+/**@brief Add block to be revoked to a transaction
+ * @param trans transaction
+ * @param lba logical block address
+ * @return standard error code*/
+int jbd_trans_revoke_block(struct jbd_trans *trans,
+ ext4_fsblk_t lba)
+{
+ struct jbd_revoke_rec *rec =
+ calloc(1, sizeof(struct jbd_revoke_rec));
+ if (!rec)
+ return ENOMEM;
+
+ rec->lba = lba;
+ LIST_INSERT_HEAD(&trans->revoke_list, rec, revoke_node);
+ return EOK;
+}
+
+/**@brief Try to add block to be revoked to a transaction.
+ * If @lba still remains in an transaction on checkpoint
+ * queue, add @lba as a revoked block to the transaction.
+ * @param trans transaction
+ * @param lba logical block address
+ * @return standard error code*/
+int jbd_trans_try_revoke_block(struct jbd_trans *trans,
+ ext4_fsblk_t lba)
+{
+ int r = EOK;
+ struct jbd_journal *journal = trans->journal;
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ struct jbd_block_rec *block_rec =
+ jbd_trans_block_rec_lookup(journal, lba);
+
+ /* Make sure we don't flush any buffers belong to this transaction. */
+ if (block_rec && block_rec->trans != trans) {
+ /* If the buffer has not been flushed yet, flush it now. */
+ if (block_rec->buf) {
+ r = ext4_block_flush_buf(fs->bdev, block_rec->buf);
+ if (r != EOK)
+ return r;
+
+ }
+
+ jbd_trans_revoke_block(trans, lba);
+ }
+
+ return EOK;
+}
+
+/**@brief Free a transaction
+ * @param journal current journal session
+ * @param trans transaction
+ * @param abort discard all the modifications on the block?
+ * @return standard error code*/
+void jbd_journal_free_trans(struct jbd_journal *journal,
+ struct jbd_trans *trans,
+ bool abort)
+{
+ struct jbd_buf *jbd_buf, *tmp;
+ struct jbd_revoke_rec *rec, *tmp2;
+ struct jbd_block_rec *block_rec, *tmp3;
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node,
+ tmp) {
+ block_rec = jbd_buf->block_rec;
+ if (abort) {
+ jbd_buf->block.buf->end_write = NULL;
+ jbd_buf->block.buf->end_write_arg = NULL;
+ ext4_bcache_clear_dirty(jbd_buf->block.buf);
+ ext4_block_set(fs->bdev, &jbd_buf->block);
+ }
+
+ TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue,
+ jbd_buf,
+ dirty_buf_node);
+ jbd_trans_finish_callback(journal,
+ trans,
+ block_rec,
+ abort);
+ TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node);
+ free(jbd_buf);
+ }
+ LIST_FOREACH_SAFE(rec, &trans->revoke_list, revoke_node,
+ tmp2) {
+ LIST_REMOVE(rec, revoke_node);
+ free(rec);
+ }
+ LIST_FOREACH_SAFE(block_rec, &trans->tbrec_list, tbrec_node,
+ tmp3) {
+ jbd_trans_remove_block_rec(journal, block_rec, trans);
+ }
+
+ free(trans);
+}
+
+/**@brief Write commit block for a transaction
+ * @param trans transaction
+ * @return standard error code*/
+static int jbd_trans_write_commit_block(struct jbd_trans *trans)
+{
+ int rc;
+ struct jbd_commit_header *header;
+ uint32_t commit_iblock = 0;
+ struct ext4_block commit_block;
+ struct jbd_journal *journal = trans->journal;
+
+ commit_iblock = jbd_journal_alloc_block(journal, trans);
+ rc = jbd_block_get_noread(journal->jbd_fs,
+ &commit_block, commit_iblock);
+ if (rc != EOK)
+ return rc;
+
+ header = (struct jbd_commit_header *)commit_block.data;
+ jbd_set32(&header->header, magic, JBD_MAGIC_NUMBER);
+ jbd_set32(&header->header, blocktype, JBD_COMMIT_BLOCK);
+ jbd_set32(&header->header, sequence, trans->trans_id);
+
+ if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb,
+ JBD_FEATURE_COMPAT_CHECKSUM)) {
+ jbd_set32(header, chksum_type, JBD_CRC32_CHKSUM);
+ jbd_set32(header, chksum_size, JBD_CRC32_CHKSUM_SIZE);
+ jbd_set32(header, chksum[0], trans->data_csum);
+ }
+ jbd_commit_csum_set(journal->jbd_fs, header);
+ ext4_bcache_set_dirty(commit_block.buf);
+ rc = jbd_block_set(journal->jbd_fs, &commit_block);
+ if (rc != EOK)
+ return rc;
+
+ return EOK;
+}
+
+/**@brief Write descriptor block for a transaction
+ * @param journal current journal session
+ * @param trans transaction
+ * @return standard error code*/
+static int jbd_journal_prepare(struct jbd_journal *journal,
+ struct jbd_trans *trans)
+{
+ int rc = EOK, i = 0;
+ int32_t tag_tbl_size;
+ uint32_t desc_iblock = 0;
+ uint32_t data_iblock = 0;
+ char *tag_start = NULL, *tag_ptr = NULL;
+ struct jbd_buf *jbd_buf, *tmp;
+ struct ext4_block desc_block, data_block;
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ uint32_t checksum = EXT4_CRC32_INIT;
+
+ /* Try to remove any non-dirty buffers from the tail of
+ * buf_queue. */
+ TAILQ_FOREACH_REVERSE_SAFE(jbd_buf, &trans->buf_queue,
+ jbd_trans_buf, buf_node, tmp) {
+ /* We stop the iteration when we find a dirty buffer. */
+ if (ext4_bcache_test_flag(jbd_buf->block.buf,
+ BC_DIRTY))
+ break;
+
+ TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue,
+ jbd_buf,
+ dirty_buf_node);
+
+ jbd_trans_finish_callback(journal,
+ trans,
+ jbd_buf->block_rec,
+ false);
+
+ /* The buffer has not been modified, just release
+ * that jbd_buf. */
+ jbd_trans_remove_block_rec(journal,
+ jbd_buf->block_rec, trans);
+ trans->data_cnt--;
+
+ jbd_buf->block.buf->end_write = NULL;
+ jbd_buf->block.buf->end_write_arg = NULL;
+ ext4_block_set(fs->bdev, &jbd_buf->block);
+ TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node);
+ free(jbd_buf);
+ }
+
+ TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, tmp) {
+ struct tag_info tag_info;
+ bool uuid_exist = false;
+ if (!ext4_bcache_test_flag(jbd_buf->block.buf,
+ BC_DIRTY)) {
+ TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue,
+ jbd_buf,
+ dirty_buf_node);
+
+ jbd_trans_finish_callback(journal,
+ trans,
+ jbd_buf->block_rec,
+ false);
+
+ /* The buffer has not been modified, just release
+ * that jbd_buf. */
+ jbd_trans_remove_block_rec(journal,
+ jbd_buf->block_rec, trans);
+ trans->data_cnt--;
+
+ jbd_buf->block.buf->end_write = NULL;
+ jbd_buf->block.buf->end_write_arg = NULL;
+ ext4_block_set(fs->bdev, &jbd_buf->block);
+ TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node);
+ free(jbd_buf);
+ continue;
+ }
+ checksum = jbd_block_csum(journal->jbd_fs,
+ jbd_buf->block.data,
+ checksum,
+ trans->trans_id);
+again:
+ if (!desc_iblock) {
+ struct jbd_bhdr *bhdr;
+ desc_iblock = jbd_journal_alloc_block(journal, trans);
+ rc = jbd_block_get_noread(journal->jbd_fs,
+ &desc_block, desc_iblock);
+ if (rc != EOK)
+ break;
+
+ ext4_bcache_set_dirty(desc_block.buf);
+
+ bhdr = (struct jbd_bhdr *)desc_block.data;
+ jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER);
+ jbd_set32(bhdr, blocktype, JBD_DESCRIPTOR_BLOCK);
+ jbd_set32(bhdr, sequence, trans->trans_id);
+
+ tag_start = (char *)(bhdr + 1);
+ tag_ptr = tag_start;
+ uuid_exist = true;
+ tag_tbl_size = journal->block_size -
+ sizeof(struct jbd_bhdr);
+
+ if (jbd_has_csum(&journal->jbd_fs->sb))
+ tag_tbl_size -= sizeof(struct jbd_block_tail);
+
+ if (!trans->start_iblock)
+ trans->start_iblock = desc_iblock;
+
+ }
+ tag_info.block = jbd_buf->block.lb_id;
+ tag_info.uuid_exist = uuid_exist;
+ if (i == trans->data_cnt - 1)
+ tag_info.last_tag = true;
+ else
+ tag_info.last_tag = false;
+
+ tag_info.checksum = checksum;
+
+ if (uuid_exist)
+ memcpy(tag_info.uuid, journal->jbd_fs->sb.uuid,
+ UUID_SIZE);
+
+ rc = jbd_write_block_tag(journal->jbd_fs,
+ tag_ptr,
+ tag_tbl_size,
+ &tag_info);
+ if (rc != EOK) {
+ jbd_meta_csum_set(journal->jbd_fs,
+ (struct jbd_bhdr *)desc_block.data);
+ jbd_block_set(journal->jbd_fs, &desc_block);
+ desc_iblock = 0;
+ goto again;
+ }
+
+ data_iblock = jbd_journal_alloc_block(journal, trans);
+ rc = jbd_block_get_noread(journal->jbd_fs,
+ &data_block, data_iblock);
+ if (rc != EOK)
+ break;
+
+ ext4_bcache_set_dirty(data_block.buf);
+
+ memcpy(data_block.data, jbd_buf->block.data,
+ journal->block_size);
+ jbd_buf->jbd_lba = data_block.lb_id;
+
+ rc = jbd_block_set(journal->jbd_fs, &data_block);
+ if (rc != EOK)
+ break;
+
+ tag_ptr += tag_info.tag_bytes;
+ tag_tbl_size -= tag_info.tag_bytes;
+
+ i++;
+ }
+ if (rc == EOK && desc_iblock) {
+ jbd_meta_csum_set(journal->jbd_fs,
+ (struct jbd_bhdr *)desc_block.data);
+ trans->data_csum = checksum;
+ jbd_block_set(journal->jbd_fs, &desc_block);
+ }
+
+ return rc;
+}
+
+/**@brief Write revoke block for a transaction
+ * @param journal current journal session
+ * @param trans transaction
+ * @return standard error code*/
+static int
+jbd_journal_prepare_revoke(struct jbd_journal *journal,
+ struct jbd_trans *trans)
+{
+ int rc = EOK, i = 0;
+ int32_t tag_tbl_size;
+ uint32_t desc_iblock = 0;
+ char *blocks_entry = NULL;
+ struct jbd_revoke_rec *rec, *tmp;
+ struct ext4_block desc_block;
+ struct jbd_revoke_header *header = NULL;
+ int32_t record_len = 4;
+
+ if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb,
+ JBD_FEATURE_INCOMPAT_64BIT))
+ record_len = 8;
+
+ LIST_FOREACH_SAFE(rec, &trans->revoke_list, revoke_node,
+ tmp) {
+again:
+ if (!desc_iblock) {
+ struct jbd_bhdr *bhdr;
+ desc_iblock = jbd_journal_alloc_block(journal, trans);
+ rc = jbd_block_get_noread(journal->jbd_fs,
+ &desc_block, desc_iblock);
+ if (rc != EOK) {
+ break;
+ }
+
+ ext4_bcache_set_dirty(desc_block.buf);
+
+ bhdr = (struct jbd_bhdr *)desc_block.data;
+ jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER);
+ jbd_set32(bhdr, blocktype, JBD_REVOKE_BLOCK);
+ jbd_set32(bhdr, sequence, trans->trans_id);
+
+ header = (struct jbd_revoke_header *)bhdr;
+ blocks_entry = (char *)(header + 1);
+ tag_tbl_size = journal->block_size -
+ sizeof(struct jbd_revoke_header);
+
+ if (jbd_has_csum(&journal->jbd_fs->sb))
+ tag_tbl_size -= sizeof(struct jbd_block_tail);
+
+ if (!trans->start_iblock)
+ trans->start_iblock = desc_iblock;
+
+ }
+
+ if (tag_tbl_size < record_len) {
+ jbd_set32(header, count,
+ journal->block_size - tag_tbl_size);
+ jbd_meta_csum_set(journal->jbd_fs,
+ (struct jbd_bhdr *)desc_block.data);
+ jbd_block_set(journal->jbd_fs, &desc_block);
+ desc_iblock = 0;
+ header = NULL;
+ goto again;
+ }
+ if (record_len == 8) {
+ uint64_t *blocks =
+ (uint64_t *)blocks_entry;
+ *blocks = to_be64(rec->lba);
+ } else {
+ uint32_t *blocks =
+ (uint32_t *)blocks_entry;
+ *blocks = to_be32(rec->lba);
+ }
+ blocks_entry += record_len;
+ tag_tbl_size -= record_len;
+
+ i++;
+ }
+ if (rc == EOK && desc_iblock) {
+ if (header != NULL)
+ jbd_set32(header, count,
+ journal->block_size - tag_tbl_size);
+
+ jbd_meta_csum_set(journal->jbd_fs,
+ (struct jbd_bhdr *)desc_block.data);
+ jbd_block_set(journal->jbd_fs, &desc_block);
+ }
+
+ return rc;
+}
+
+/**@brief Submit the transaction to transaction queue.
+ * @param journal current journal session
+ * @param trans transaction*/
+void
+jbd_journal_submit_trans(struct jbd_journal *journal,
+ struct jbd_trans *trans)
+{
+ TAILQ_INSERT_TAIL(&journal->trans_queue,
+ trans,
+ trans_node);
+}
+
+/**@brief Put references of block descriptors in a transaction.
+ * @param journal current journal session
+ * @param trans transaction*/
+void jbd_journal_cp_trans(struct jbd_journal *journal, struct jbd_trans *trans)
+{
+ struct jbd_buf *jbd_buf, *tmp;
+ struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs;
+ TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node,
+ tmp) {
+ struct ext4_block block = jbd_buf->block;
+ ext4_block_set(fs->bdev, &block);
+ }
+}
+
+/**@brief Update the start block of the journal when
+ * all the contents in a transaction reach the disk.*/
+static void jbd_trans_end_write(struct ext4_bcache *bc __unused,
+ struct ext4_buf *buf,
+ int res,
+ void *arg)
+{
+ struct jbd_buf *jbd_buf = arg;
+ struct jbd_trans *trans = jbd_buf->trans;
+ struct jbd_journal *journal = trans->journal;
+ bool first_in_queue =
+ trans == TAILQ_FIRST(&journal->cp_queue);
+ if (res != EOK)
+ trans->error = res;
+
+ TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node);
+ TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue,
+ jbd_buf,
+ dirty_buf_node);
+ jbd_trans_finish_callback(journal,
+ trans,
+ jbd_buf->block_rec,
+ false);
+ jbd_buf->block_rec->buf = NULL;
+ free(jbd_buf);
+
+ /* Clear the end_write and end_write_arg fields. */
+ buf->end_write = NULL;
+ buf->end_write_arg = NULL;
+
+ trans->written_cnt++;
+ if (trans->written_cnt == trans->data_cnt) {
+ /* If it is the first transaction on checkpoint queue,
+ * we will shift the start of the journal to the next
+ * transaction, and remove subsequent written
+ * transactions from checkpoint queue until we find
+ * an unwritten one. */
+ if (first_in_queue) {
+ journal->start = trans->start_iblock +
+ trans->alloc_blocks;
+ wrap(&journal->jbd_fs->sb, journal->start);
+ journal->trans_id = trans->trans_id + 1;
+ TAILQ_REMOVE(&journal->cp_queue, trans, trans_node);
+ jbd_journal_free_trans(journal, trans, false);
+
+ jbd_journal_purge_cp_trans(journal, false);
+ jbd_journal_write_sb(journal);
+ jbd_write_sb(journal->jbd_fs);
+ }
+ }
+}
+
+/**@brief Commit a transaction to the journal immediately.
+ * @param journal current journal session
+ * @param trans transaction
+ * @return standard error code*/
+int jbd_journal_commit_trans(struct jbd_journal *journal,
+ struct jbd_trans *trans)
+{
+ int rc = EOK;
+ uint32_t last = journal->last;
+
+ trans->trans_id = journal->alloc_trans_id;
+ rc = jbd_journal_prepare(journal, trans);
+ if (rc != EOK)
+ goto Finish;
+
+ rc = jbd_journal_prepare_revoke(journal, trans);
+ if (rc != EOK)
+ goto Finish;
+
+ if (TAILQ_EMPTY(&trans->buf_queue) &&
+ LIST_EMPTY(&trans->revoke_list)) {
+ /* Since there are no entries in both buffer list
+ * and revoke entry list, we do not consider trans as
+ * complete transaction and just return EOK.*/
+ jbd_journal_free_trans(journal, trans, false);
+ goto Finish;
+ }
+
+ rc = jbd_trans_write_commit_block(trans);
+ if (rc != EOK)
+ goto Finish;
+
+ journal->alloc_trans_id++;
+ if (TAILQ_EMPTY(&journal->cp_queue)) {
+ if (trans->data_cnt) {
+ journal->start = trans->start_iblock;
+ wrap(&journal->jbd_fs->sb, journal->start);
+ journal->trans_id = trans->trans_id;
+ jbd_journal_write_sb(journal);
+ jbd_write_sb(journal->jbd_fs);
+ TAILQ_INSERT_TAIL(&journal->cp_queue, trans,
+ trans_node);
+ jbd_journal_cp_trans(journal, trans);
+ } else {
+ journal->start = trans->start_iblock +
+ trans->alloc_blocks;
+ wrap(&journal->jbd_fs->sb, journal->start);
+ journal->trans_id = trans->trans_id + 1;
+ jbd_journal_write_sb(journal);
+ jbd_journal_free_trans(journal, trans, false);
+ }
+ } else {
+ TAILQ_INSERT_TAIL(&journal->cp_queue, trans,
+ trans_node);
+ if (trans->data_cnt)
+ jbd_journal_cp_trans(journal, trans);
+
+ }
+Finish:
+ if (rc != EOK) {
+ journal->last = last;
+ jbd_journal_free_trans(journal, trans, true);
+ }
+ return rc;
+}
+
+/**@brief Commit one transaction on transaction queue
+ * to the journal.
+ * @param journal current journal session.*/
+void jbd_journal_commit_one(struct jbd_journal *journal)
+{
+ struct jbd_trans *trans;
+
+ if ((trans = TAILQ_FIRST(&journal->trans_queue))) {
+ TAILQ_REMOVE(&journal->trans_queue, trans, trans_node);
+ jbd_journal_commit_trans(journal, trans);
+ }
+}
+
+/**@brief Commit all the transactions on transaction queue
+ * to the journal.
+ * @param journal current journal session.*/
+void jbd_journal_commit_all(struct jbd_journal *journal)
+{
+ while (!TAILQ_EMPTY(&journal->trans_queue)) {
+ jbd_journal_commit_one(journal);
+ }
+}
+
+/**
+ * @}
+ */