From: Vyacheslav Dubeyko <slava@xxxxxxxxxxx> Subject: [PATCH v3 14/15] hfsplus: implement replay journal functionality This patch implements functionality of HFS+ journal replay. If the journal contains valid transaction then it needs to write them to disk. In order to replay the journal, an implementation just loops over the transactions, copying each individual block in the transaction from the journal to its proper location on the volume. Once those blocks have been flushed to the media (not just the driver!), it may update the journal header to remove the transactions. Signed-off-by: Vyacheslav Dubeyko <slava@xxxxxxxxxxx> CC: Al Viro <viro@xxxxxxxxxxxxxxxxxx> CC: Christoph Hellwig <hch@xxxxxxxxxxxxx> Tested-by: Hin-Tak Leung <htl10@xxxxxxxxxxxxxxxxxxxxx> --- fs/hfsplus/journal.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/fs/hfsplus/journal.c b/fs/hfsplus/journal.c index c6547c4..913e629 100644 --- a/fs/hfsplus/journal.c +++ b/fs/hfsplus/journal.c @@ -688,6 +688,30 @@ static int hfsplus_replay_transaction_block(struct super_block *sb, int i, } static inline +bool hfsplus_journal_placement_valid(struct hfsplus_journal *jnl) +{ + if (JOURNAL_SIZE(jnl->jh) != be64_to_cpu(jnl->jib->size)) { + pr_err("corrupted journal header\n"); + hfs_dbg(JOURNAL, "jh_hdr->size %llu, jib_hdr.size %llu\n", + JOURNAL_SIZE(jnl->jh), + be64_to_cpu(jnl->jib->size)); + return false; + } + + if (TR_START(jnl->jh) > JOURNAL_SIZE(jnl->jh) || + LAST_TR_END(jnl->jh) > JOURNAL_SIZE(jnl->jh)) { + pr_err("corrupted journal header\n"); + hfs_dbg(JOURNAL, "start %llu, end %llu, size %llu\n", + TR_START(jnl->jh), + LAST_TR_END(jnl->jh), + JOURNAL_SIZE(jnl->jh)); + return false; + } + + return true; +} + +static inline bool hfsplus_journal_empty(struct hfsplus_journal_header *jh) { return TR_START(jh) == LAST_TR_END(jh); @@ -752,6 +776,68 @@ int hfsplus_verify_blhdr(u32 prev_seq_num, struct hfsplus_blhdr *blhdr) return 0; } +/* + * hfsplus_replay_journal - replay journal + * + * @sb: superblock + * + * If the journal contains valid transaction then it needs + * to write them to disk. Return success if it brings the + * file system into consistent state. Otherwise, it fails. + * + * Technical Note TN1150 + * + * In order to replay the journal, an implementation just loops over + * the transactions, copying each individual block in the transaction + * from the journal to its proper location on the volume. Once those + * blocks have been flushed to the media (not just the driver!), it may + * update the journal header to remove the transactions. + * + * Here are the steps to replay the journal: + * (1) Read the volume header into variable vhb. The volume may have + * an HFS wrapper; if so, you will need to use it to determine the + * location of the volume header. + * (2) Test the kHFSVolumeJournaledBit in the attributes field of the + * volume header. If it is not set, there is no journal to replay, + * and you are done. + * (3) Read the journal info block from the allocation block number + * vhb.journalInfoBlock, into variable jib. + * (4) If kJIJournalNeedsInitMask is set in jib.flags, the journal was + * never initialized, so there is no journal to replay. + * (5) Verify that kJIJournalInFSMask is set and kJIJournalOnOtherDeviceMask + * is clear in jib.flags. + * (6) Read the journal header at jib.offset bytes from the start of + * the volume, and place it in variable jhdr. + * (7) If jhdr.start equals jhdr.end, the journal does not have any + * transactions, so there is nothing to replay. + * (8) Set the current offset in the journal (typically a local variable) + * to the start of the journal buffer, jhdr.start. + * (9) While jhdr.start does not equal jhdr.end, perform the following steps: + * (a) Read a block list header of jhdr.blhdr_size bytes from the current + * offset in the journal into variable blhdr. + * (b) For each block in bhdr.binfo[1] to bhdr.binfo[blhdr.num_blocks], + * inclusive, copy bsize bytes from the current offset in the journal + * to sector bnum on the volume (to byte offset bnum*jdhr.jhdr_size). + * Remember that jhdr_size is the size of a sector, in bytes. + * (c) If bhdr.binfo[0].next is zero, you have completed the last block + * list of the current transaction; set jhdr.start to the current + * offset in the journal. + * + * Remember that the journal is a circular buffer. When reading a block list + * header or block from the journal buffer (in the loop described above), you + * will need to check whether it wraps around the end of the journal buffer. + * If it would extend beyond the end of the journal buffer, you must stop + * reading at the end of the journal buffer, and resume reading at the start + * of the journal buffer (offset jhdr.jhdr_size bytes from the start of + * the journal). + * + * After replaying an entire transaction (all blocks in a block list, + * when bhdr.binfo[0] is zero), or after replaying all transactions, + * you may update the value of the start field in the journal header + * to the current offset in the journal. This will remove those block + * lists from the journal since they have been written to their correct + * locations on disk. + */ static int hfsplus_replay_journal(struct super_block *sb) { struct hfsplus_journal *jnl = HFSPLUS_SB(sb)->jnl; @@ -760,22 +846,40 @@ static int hfsplus_replay_journal(struct super_block *sb) u32 last_seq_num = 0; int err; + hfs_dbg(JREPLAY, "try to replay journal\n"); + + if (hfsplus_journal_empty(jh)) { + hfs_dbg(JREPLAY, "journal is empty, nothing to replay\n"); + return hfsplus_replay_journal_header(sb); + } + + if (!hfsplus_journal_placement_valid(jnl)) { + pr_err("invalid start/end offset in journal header\n"); + return -EIO; + } + err = hfsplus_init_block_list_desc(sb, &desc); if (unlikely(err)) { pr_err("unable to initialize block list descriptor\n"); return err; } + mutex_lock(&jnl->jnl_lock); + /* Go through transactions */ while (!hfsplus_journal_empty(jh)) { struct hfsplus_blhdr *blhdr; struct hfsplus_block_info *binfo; + __le64 saved_trans_start = 0; u32 really_used = 0; u32 i; if (TR_START(jh) == JOURNAL_SIZE(jh)) __hfsplus_wrap_journal(sb); + hfs_dbg(JREPLAY, "start: %llu, start_sector: %llu\n", + TR_START(jh), JOURNAL_OFF_TO_SEC(sb)); + blhdr = hfsplus_get_blhdr(sb, JOURNAL_OFF_TO_SEC(sb), &desc); if (!blhdr) { err = -EIO; @@ -801,6 +905,7 @@ static int hfsplus_replay_journal(struct super_block *sb) le64_add_cpu(&jh->start, BLHDR_SIZE(jh)); really_used += BLHDR_SIZE(jh); + saved_trans_start = jh->start; /* Check transaction */ for (i = 1; i < TR_BLOCKS(blhdr); i++) { @@ -820,6 +925,16 @@ static int hfsplus_replay_journal(struct super_block *sb) really_used += BSIZE(binfo); } + if (really_used != TR_BYTES(blhdr)) { + pr_err("transaction is corrupted\n"); + hfs_dbg(JREPLAY, "bytes_used: %u, really used: %u\n", + TR_BYTES(blhdr), really_used); + err = -EIO; + goto failed_journal_replay; + } + + jh->start = saved_trans_start; + /* Replay transaction */ for (i = 1; i < TR_BLOCKS(blhdr); i++) { binfo = hfsplus_get_binfo(sb, i, &desc); @@ -835,10 +950,15 @@ static int hfsplus_replay_journal(struct super_block *sb) } } - /* TODO: implement */ - return -EINVAL; + if (hfsplus_journal_empty(jh)) + err = hfsplus_replay_journal_header(sb); + else { + pr_err("journal replay failed\n"); + err = -EIO; + } failed_journal_replay: + mutex_unlock(&jnl->jnl_lock); hfsplus_deinit_block_list_desc(&desc); return err; } -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html