mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
394 lines
10 KiB
394 lines
10 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
#include <linux/fs.h> |
|
#include <linux/random.h> |
|
#include <linux/buffer_head.h> |
|
#include <linux/utsname.h> |
|
#include <linux/kthread.h> |
|
|
|
#include "ext4.h" |
|
|
|
/* Checksumming functions */ |
|
static __le32 ext4_mmp_csum(struct super_block *sb, struct mmp_struct *mmp) |
|
{ |
|
struct ext4_sb_info *sbi = EXT4_SB(sb); |
|
int offset = offsetof(struct mmp_struct, mmp_checksum); |
|
__u32 csum; |
|
|
|
csum = ext4_chksum(sbi, sbi->s_csum_seed, (char *)mmp, offset); |
|
|
|
return cpu_to_le32(csum); |
|
} |
|
|
|
static int ext4_mmp_csum_verify(struct super_block *sb, struct mmp_struct *mmp) |
|
{ |
|
if (!ext4_has_metadata_csum(sb)) |
|
return 1; |
|
|
|
return mmp->mmp_checksum == ext4_mmp_csum(sb, mmp); |
|
} |
|
|
|
static void ext4_mmp_csum_set(struct super_block *sb, struct mmp_struct *mmp) |
|
{ |
|
if (!ext4_has_metadata_csum(sb)) |
|
return; |
|
|
|
mmp->mmp_checksum = ext4_mmp_csum(sb, mmp); |
|
} |
|
|
|
/* |
|
* Write the MMP block using REQ_SYNC to try to get the block on-disk |
|
* faster. |
|
*/ |
|
static int write_mmp_block(struct super_block *sb, struct buffer_head *bh) |
|
{ |
|
struct mmp_struct *mmp = (struct mmp_struct *)(bh->b_data); |
|
|
|
/* |
|
* We protect against freezing so that we don't create dirty buffers |
|
* on frozen filesystem. |
|
*/ |
|
sb_start_write(sb); |
|
ext4_mmp_csum_set(sb, mmp); |
|
lock_buffer(bh); |
|
bh->b_end_io = end_buffer_write_sync; |
|
get_bh(bh); |
|
submit_bh(REQ_OP_WRITE, REQ_SYNC | REQ_META | REQ_PRIO, bh); |
|
wait_on_buffer(bh); |
|
sb_end_write(sb); |
|
if (unlikely(!buffer_uptodate(bh))) |
|
return -EIO; |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Read the MMP block. It _must_ be read from disk and hence we clear the |
|
* uptodate flag on the buffer. |
|
*/ |
|
static int read_mmp_block(struct super_block *sb, struct buffer_head **bh, |
|
ext4_fsblk_t mmp_block) |
|
{ |
|
struct mmp_struct *mmp; |
|
int ret; |
|
|
|
if (*bh) |
|
clear_buffer_uptodate(*bh); |
|
|
|
/* This would be sb_bread(sb, mmp_block), except we need to be sure |
|
* that the MD RAID device cache has been bypassed, and that the read |
|
* is not blocked in the elevator. */ |
|
if (!*bh) { |
|
*bh = sb_getblk(sb, mmp_block); |
|
if (!*bh) { |
|
ret = -ENOMEM; |
|
goto warn_exit; |
|
} |
|
} |
|
|
|
lock_buffer(*bh); |
|
ret = ext4_read_bh(*bh, REQ_META | REQ_PRIO, NULL); |
|
if (ret) |
|
goto warn_exit; |
|
|
|
mmp = (struct mmp_struct *)((*bh)->b_data); |
|
if (le32_to_cpu(mmp->mmp_magic) != EXT4_MMP_MAGIC) { |
|
ret = -EFSCORRUPTED; |
|
goto warn_exit; |
|
} |
|
if (!ext4_mmp_csum_verify(sb, mmp)) { |
|
ret = -EFSBADCRC; |
|
goto warn_exit; |
|
} |
|
return 0; |
|
warn_exit: |
|
brelse(*bh); |
|
*bh = NULL; |
|
ext4_warning(sb, "Error %d while reading MMP block %llu", |
|
ret, mmp_block); |
|
return ret; |
|
} |
|
|
|
/* |
|
* Dump as much information as possible to help the admin. |
|
*/ |
|
void __dump_mmp_msg(struct super_block *sb, struct mmp_struct *mmp, |
|
const char *function, unsigned int line, const char *msg) |
|
{ |
|
__ext4_warning(sb, function, line, "%s", msg); |
|
__ext4_warning(sb, function, line, |
|
"MMP failure info: last update time: %llu, last update node: %.*s, last update device: %.*s", |
|
(unsigned long long)le64_to_cpu(mmp->mmp_time), |
|
(int)sizeof(mmp->mmp_nodename), mmp->mmp_nodename, |
|
(int)sizeof(mmp->mmp_bdevname), mmp->mmp_bdevname); |
|
} |
|
|
|
/* |
|
* kmmpd will update the MMP sequence every s_mmp_update_interval seconds |
|
*/ |
|
static int kmmpd(void *data) |
|
{ |
|
struct super_block *sb = (struct super_block *) data; |
|
struct ext4_super_block *es = EXT4_SB(sb)->s_es; |
|
struct buffer_head *bh = EXT4_SB(sb)->s_mmp_bh; |
|
struct mmp_struct *mmp; |
|
ext4_fsblk_t mmp_block; |
|
u32 seq = 0; |
|
unsigned long failed_writes = 0; |
|
int mmp_update_interval = le16_to_cpu(es->s_mmp_update_interval); |
|
unsigned mmp_check_interval; |
|
unsigned long last_update_time; |
|
unsigned long diff; |
|
int retval; |
|
|
|
mmp_block = le64_to_cpu(es->s_mmp_block); |
|
mmp = (struct mmp_struct *)(bh->b_data); |
|
mmp->mmp_time = cpu_to_le64(ktime_get_real_seconds()); |
|
/* |
|
* Start with the higher mmp_check_interval and reduce it if |
|
* the MMP block is being updated on time. |
|
*/ |
|
mmp_check_interval = max(EXT4_MMP_CHECK_MULT * mmp_update_interval, |
|
EXT4_MMP_MIN_CHECK_INTERVAL); |
|
mmp->mmp_check_interval = cpu_to_le16(mmp_check_interval); |
|
BUILD_BUG_ON(sizeof(mmp->mmp_bdevname) < BDEVNAME_SIZE); |
|
bdevname(bh->b_bdev, mmp->mmp_bdevname); |
|
|
|
memcpy(mmp->mmp_nodename, init_utsname()->nodename, |
|
sizeof(mmp->mmp_nodename)); |
|
|
|
while (!kthread_should_stop() && !sb_rdonly(sb)) { |
|
if (!ext4_has_feature_mmp(sb)) { |
|
ext4_warning(sb, "kmmpd being stopped since MMP feature" |
|
" has been disabled."); |
|
goto wait_to_exit; |
|
} |
|
if (++seq > EXT4_MMP_SEQ_MAX) |
|
seq = 1; |
|
|
|
mmp->mmp_seq = cpu_to_le32(seq); |
|
mmp->mmp_time = cpu_to_le64(ktime_get_real_seconds()); |
|
last_update_time = jiffies; |
|
|
|
retval = write_mmp_block(sb, bh); |
|
/* |
|
* Don't spew too many error messages. Print one every |
|
* (s_mmp_update_interval * 60) seconds. |
|
*/ |
|
if (retval) { |
|
if ((failed_writes % 60) == 0) { |
|
ext4_error_err(sb, -retval, |
|
"Error writing to MMP block"); |
|
} |
|
failed_writes++; |
|
} |
|
|
|
diff = jiffies - last_update_time; |
|
if (diff < mmp_update_interval * HZ) |
|
schedule_timeout_interruptible(mmp_update_interval * |
|
HZ - diff); |
|
|
|
/* |
|
* We need to make sure that more than mmp_check_interval |
|
* seconds have not passed since writing. If that has happened |
|
* we need to check if the MMP block is as we left it. |
|
*/ |
|
diff = jiffies - last_update_time; |
|
if (diff > mmp_check_interval * HZ) { |
|
struct buffer_head *bh_check = NULL; |
|
struct mmp_struct *mmp_check; |
|
|
|
retval = read_mmp_block(sb, &bh_check, mmp_block); |
|
if (retval) { |
|
ext4_error_err(sb, -retval, |
|
"error reading MMP data: %d", |
|
retval); |
|
goto wait_to_exit; |
|
} |
|
|
|
mmp_check = (struct mmp_struct *)(bh_check->b_data); |
|
if (mmp->mmp_seq != mmp_check->mmp_seq || |
|
memcmp(mmp->mmp_nodename, mmp_check->mmp_nodename, |
|
sizeof(mmp->mmp_nodename))) { |
|
dump_mmp_msg(sb, mmp_check, |
|
"Error while updating MMP info. " |
|
"The filesystem seems to have been" |
|
" multiply mounted."); |
|
ext4_error_err(sb, EBUSY, "abort"); |
|
put_bh(bh_check); |
|
retval = -EBUSY; |
|
goto wait_to_exit; |
|
} |
|
put_bh(bh_check); |
|
} |
|
|
|
/* |
|
* Adjust the mmp_check_interval depending on how much time |
|
* it took for the MMP block to be written. |
|
*/ |
|
mmp_check_interval = max(min(EXT4_MMP_CHECK_MULT * diff / HZ, |
|
EXT4_MMP_MAX_CHECK_INTERVAL), |
|
EXT4_MMP_MIN_CHECK_INTERVAL); |
|
mmp->mmp_check_interval = cpu_to_le16(mmp_check_interval); |
|
} |
|
|
|
/* |
|
* Unmount seems to be clean. |
|
*/ |
|
mmp->mmp_seq = cpu_to_le32(EXT4_MMP_SEQ_CLEAN); |
|
mmp->mmp_time = cpu_to_le64(ktime_get_real_seconds()); |
|
|
|
retval = write_mmp_block(sb, bh); |
|
|
|
wait_to_exit: |
|
while (!kthread_should_stop()) { |
|
set_current_state(TASK_INTERRUPTIBLE); |
|
if (!kthread_should_stop()) |
|
schedule(); |
|
} |
|
set_current_state(TASK_RUNNING); |
|
return retval; |
|
} |
|
|
|
void ext4_stop_mmpd(struct ext4_sb_info *sbi) |
|
{ |
|
if (sbi->s_mmp_tsk) { |
|
kthread_stop(sbi->s_mmp_tsk); |
|
brelse(sbi->s_mmp_bh); |
|
sbi->s_mmp_tsk = NULL; |
|
} |
|
} |
|
|
|
/* |
|
* Get a random new sequence number but make sure it is not greater than |
|
* EXT4_MMP_SEQ_MAX. |
|
*/ |
|
static unsigned int mmp_new_seq(void) |
|
{ |
|
u32 new_seq; |
|
|
|
do { |
|
new_seq = prandom_u32(); |
|
} while (new_seq > EXT4_MMP_SEQ_MAX); |
|
|
|
return new_seq; |
|
} |
|
|
|
/* |
|
* Protect the filesystem from being mounted more than once. |
|
*/ |
|
int ext4_multi_mount_protect(struct super_block *sb, |
|
ext4_fsblk_t mmp_block) |
|
{ |
|
struct ext4_super_block *es = EXT4_SB(sb)->s_es; |
|
struct buffer_head *bh = NULL; |
|
struct mmp_struct *mmp = NULL; |
|
u32 seq; |
|
unsigned int mmp_check_interval = le16_to_cpu(es->s_mmp_update_interval); |
|
unsigned int wait_time = 0; |
|
int retval; |
|
|
|
if (mmp_block < le32_to_cpu(es->s_first_data_block) || |
|
mmp_block >= ext4_blocks_count(es)) { |
|
ext4_warning(sb, "Invalid MMP block in superblock"); |
|
goto failed; |
|
} |
|
|
|
retval = read_mmp_block(sb, &bh, mmp_block); |
|
if (retval) |
|
goto failed; |
|
|
|
mmp = (struct mmp_struct *)(bh->b_data); |
|
|
|
if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL) |
|
mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL; |
|
|
|
/* |
|
* If check_interval in MMP block is larger, use that instead of |
|
* update_interval from the superblock. |
|
*/ |
|
if (le16_to_cpu(mmp->mmp_check_interval) > mmp_check_interval) |
|
mmp_check_interval = le16_to_cpu(mmp->mmp_check_interval); |
|
|
|
seq = le32_to_cpu(mmp->mmp_seq); |
|
if (seq == EXT4_MMP_SEQ_CLEAN) |
|
goto skip; |
|
|
|
if (seq == EXT4_MMP_SEQ_FSCK) { |
|
dump_mmp_msg(sb, mmp, "fsck is running on the filesystem"); |
|
goto failed; |
|
} |
|
|
|
wait_time = min(mmp_check_interval * 2 + 1, |
|
mmp_check_interval + 60); |
|
|
|
/* Print MMP interval if more than 20 secs. */ |
|
if (wait_time > EXT4_MMP_MIN_CHECK_INTERVAL * 4) |
|
ext4_warning(sb, "MMP interval %u higher than expected, please" |
|
" wait.\n", wait_time * 2); |
|
|
|
if (schedule_timeout_interruptible(HZ * wait_time) != 0) { |
|
ext4_warning(sb, "MMP startup interrupted, failing mount\n"); |
|
goto failed; |
|
} |
|
|
|
retval = read_mmp_block(sb, &bh, mmp_block); |
|
if (retval) |
|
goto failed; |
|
mmp = (struct mmp_struct *)(bh->b_data); |
|
if (seq != le32_to_cpu(mmp->mmp_seq)) { |
|
dump_mmp_msg(sb, mmp, |
|
"Device is already active on another node."); |
|
goto failed; |
|
} |
|
|
|
skip: |
|
/* |
|
* write a new random sequence number. |
|
*/ |
|
seq = mmp_new_seq(); |
|
mmp->mmp_seq = cpu_to_le32(seq); |
|
|
|
retval = write_mmp_block(sb, bh); |
|
if (retval) |
|
goto failed; |
|
|
|
/* |
|
* wait for MMP interval and check mmp_seq. |
|
*/ |
|
if (schedule_timeout_interruptible(HZ * wait_time) != 0) { |
|
ext4_warning(sb, "MMP startup interrupted, failing mount"); |
|
goto failed; |
|
} |
|
|
|
retval = read_mmp_block(sb, &bh, mmp_block); |
|
if (retval) |
|
goto failed; |
|
mmp = (struct mmp_struct *)(bh->b_data); |
|
if (seq != le32_to_cpu(mmp->mmp_seq)) { |
|
dump_mmp_msg(sb, mmp, |
|
"Device is already active on another node."); |
|
goto failed; |
|
} |
|
|
|
EXT4_SB(sb)->s_mmp_bh = bh; |
|
|
|
/* |
|
* Start a kernel thread to update the MMP block periodically. |
|
*/ |
|
EXT4_SB(sb)->s_mmp_tsk = kthread_run(kmmpd, sb, "kmmpd-%.*s", |
|
(int)sizeof(mmp->mmp_bdevname), |
|
bdevname(bh->b_bdev, |
|
mmp->mmp_bdevname)); |
|
if (IS_ERR(EXT4_SB(sb)->s_mmp_tsk)) { |
|
EXT4_SB(sb)->s_mmp_tsk = NULL; |
|
ext4_warning(sb, "Unable to create kmmpd thread for %s.", |
|
sb->s_id); |
|
goto failed; |
|
} |
|
|
|
return 0; |
|
|
|
failed: |
|
brelse(bh); |
|
return 1; |
|
}
|
|
|