2011-05-25 02:31:25 +04:00
# include <linux/fs.h>
# include <linux/random.h>
# include <linux/buffer_head.h>
# include <linux/utsname.h>
# include <linux/kthread.h>
# include "ext4.h"
2012-04-30 02:47:10 +04:00
/* Checksumming functions */
2013-04-10 07:56:48 +04:00
static __le32 ext4_mmp_csum ( struct super_block * sb , struct mmp_struct * mmp )
2012-04-30 02:47:10 +04:00
{
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 ) ;
}
2014-05-12 18:50:23 +04:00
static int ext4_mmp_csum_verify ( struct super_block * sb , struct mmp_struct * mmp )
2012-04-30 02:47:10 +04:00
{
2014-10-13 11:36:16 +04:00
if ( ! ext4_has_metadata_csum ( sb ) )
2012-04-30 02:47:10 +04:00
return 1 ;
return mmp - > mmp_checksum = = ext4_mmp_csum ( sb , mmp ) ;
}
2014-05-12 18:50:23 +04:00
static void ext4_mmp_csum_set ( struct super_block * sb , struct mmp_struct * mmp )
2012-04-30 02:47:10 +04:00
{
2014-10-13 11:36:16 +04:00
if ( ! ext4_has_metadata_csum ( sb ) )
2012-04-30 02:47:10 +04:00
return ;
mmp - > mmp_checksum = ext4_mmp_csum ( sb , mmp ) ;
}
2011-05-25 02:31:25 +04:00
/*
2016-11-01 16:40:10 +03:00
* Write the MMP block using REQ_SYNC to try to get the block on - disk
2011-05-25 02:31:25 +04:00
* faster .
*/
2012-04-30 02:47:10 +04:00
static int write_mmp_block ( struct super_block * sb , struct buffer_head * bh )
2011-05-25 02:31:25 +04:00
{
2012-04-30 02:47:10 +04:00
struct mmp_struct * mmp = ( struct mmp_struct * ) ( bh - > b_data ) ;
2012-06-12 18:20:38 +04:00
/*
* We protect against freezing so that we don ' t create dirty buffers
* on frozen filesystem .
*/
sb_start_write ( sb ) ;
2012-04-30 02:47:10 +04:00
ext4_mmp_csum_set ( sb , mmp ) ;
2011-05-25 02:31:25 +04:00
mark_buffer_dirty ( bh ) ;
lock_buffer ( bh ) ;
bh - > b_end_io = end_buffer_write_sync ;
get_bh ( bh ) ;
2016-11-01 16:40:10 +03:00
submit_bh ( REQ_OP_WRITE , REQ_SYNC | REQ_META | REQ_PRIO , bh ) ;
2011-05-25 02:31:25 +04:00
wait_on_buffer ( bh ) ;
2012-06-12 18:20:38 +04:00
sb_end_write ( sb ) ;
2011-05-25 02:31:25 +04:00
if ( unlikely ( ! buffer_uptodate ( bh ) ) )
return 1 ;
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 ;
2015-08-15 18:30:31 +03:00
int ret ;
2011-05-25 02:31:25 +04:00
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 . */
2015-08-15 18:30:31 +03:00
if ( ! * bh ) {
2011-05-25 02:31:25 +04:00
* bh = sb_getblk ( sb , mmp_block ) ;
2015-08-15 18:30:31 +03:00
if ( ! * bh ) {
ret = - ENOMEM ;
goto warn_exit ;
2011-05-25 02:31:25 +04:00
}
}
2015-08-15 18:30:31 +03:00
get_bh ( * bh ) ;
lock_buffer ( * bh ) ;
( * bh ) - > b_end_io = end_buffer_read_sync ;
2016-11-01 16:40:10 +03:00
submit_bh ( REQ_OP_READ , REQ_META | REQ_PRIO , * bh ) ;
2015-08-15 18:30:31 +03:00
wait_on_buffer ( * bh ) ;
if ( ! buffer_uptodate ( * bh ) ) {
ret = - EIO ;
goto warn_exit ;
2011-05-25 02:31:25 +04:00
}
mmp = ( struct mmp_struct * ) ( ( * bh ) - > b_data ) ;
2016-03-14 00:56:52 +03:00
if ( le32_to_cpu ( mmp - > mmp_magic ) ! = EXT4_MMP_MAGIC ) {
2015-10-17 23:16:04 +03:00
ret = - EFSCORRUPTED ;
2016-03-14 00:56:52 +03:00
goto warn_exit ;
}
if ( ! ext4_mmp_csum_verify ( sb , mmp ) ) {
2015-10-17 23:16:04 +03:00
ret = - EFSBADCRC ;
2016-03-14 00:56:52 +03:00
goto warn_exit ;
}
return 0 ;
2015-08-15 18:30:31 +03:00
warn_exit :
2016-03-14 00:56:52 +03:00
brelse ( * bh ) ;
* bh = NULL ;
2015-08-15 18:30:31 +03:00
ext4_warning ( sb , " Error %d while reading MMP block %llu " ,
ret , mmp_block ) ;
return ret ;
2011-05-25 02:31:25 +04:00
}
/*
* 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 )
{
2015-08-15 18:38:13 +03:00
__ext4_warning ( sb , function , line , " %s " , msg ) ;
2011-05-25 02:31:25 +04:00
__ext4_warning ( sb , function , line ,
" MMP failure info: last update time: %llu, last update "
2016-04-27 08:11:21 +03:00
" node: %s, last update device: %s " ,
2011-05-25 02:31:25 +04:00
( long long unsigned int ) le64_to_cpu ( mmp - > mmp_time ) ,
mmp - > mmp_nodename , 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 mmpd_data * ) data ) - > sb ;
struct buffer_head * bh = ( ( struct mmpd_data * ) data ) - > bh ;
struct ext4_super_block * es = EXT4_SB ( sb ) - > s_es ;
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 ( get_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 ) ;
bdevname ( bh - > b_bdev , mmp - > mmp_bdevname ) ;
2011-10-18 18:49:51 +04:00
memcpy ( mmp - > mmp_nodename , init_utsname ( ) - > nodename ,
2011-05-25 02:31:25 +04:00
sizeof ( mmp - > mmp_nodename ) ) ;
while ( ! kthread_should_stop ( ) ) {
if ( + + seq > EXT4_MMP_SEQ_MAX )
seq = 1 ;
mmp - > mmp_seq = cpu_to_le32 ( seq ) ;
mmp - > mmp_time = cpu_to_le64 ( get_seconds ( ) ) ;
last_update_time = jiffies ;
2012-04-30 02:47:10 +04:00
retval = write_mmp_block ( sb , bh ) ;
2011-05-25 02:31:25 +04:00
/*
* Don ' t spew too many error messages . Print one every
* ( s_mmp_update_interval * 60 ) seconds .
*/
2011-10-18 18:51:51 +04:00
if ( retval ) {
if ( ( failed_writes % 60 ) = = 0 )
ext4_error ( sb , " Error writing to MMP block " ) ;
2011-05-25 02:31:25 +04:00
failed_writes + + ;
}
if ( ! ( le32_to_cpu ( es - > s_feature_incompat ) &
EXT4_FEATURE_INCOMPAT_MMP ) ) {
ext4_warning ( sb , " kmmpd being stopped since MMP feature "
" has been disabled. " ) ;
2016-03-14 00:56:52 +03:00
goto exit_thread ;
2011-05-25 02:31:25 +04:00
}
if ( sb - > s_flags & MS_RDONLY ) {
ext4_warning ( sb , " kmmpd being stopped since filesystem "
" has been remounted as readonly. " ) ;
2016-03-14 00:56:52 +03:00
goto exit_thread ;
2011-05-25 02:31:25 +04:00
}
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 ( sb , " error reading MMP data: %d " ,
retval ) ;
2016-03-14 00:56:52 +03:00
goto exit_thread ;
2011-05-25 02:31:25 +04:00
}
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 ( sb , " abort " ) ;
2016-03-14 00:56:52 +03:00
put_bh ( bh_check ) ;
retval = - EBUSY ;
goto exit_thread ;
2011-05-25 02:31:25 +04:00
}
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 ( get_seconds ( ) ) ;
2012-04-30 02:47:10 +04:00
retval = write_mmp_block ( sb , bh ) ;
2011-05-25 02:31:25 +04:00
2016-03-14 00:56:52 +03:00
exit_thread :
EXT4_SB ( sb ) - > s_mmp_tsk = NULL ;
2011-05-25 02:31:25 +04:00
kfree ( data ) ;
brelse ( bh ) ;
return retval ;
}
/*
* 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 {
2013-11-08 09:14:53 +04:00
new_seq = prandom_u32 ( ) ;
2011-05-25 02:31:25 +04:00
} 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 ;
struct mmpd_data * mmpd_data ;
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 .
*/
2012-02-27 10:09:03 +04:00
if ( le16_to_cpu ( mmp - > mmp_check_interval ) > mmp_check_interval )
mmp_check_interval = le16_to_cpu ( mmp - > mmp_check_interval ) ;
2011-05-25 02:31:25 +04:00
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 .
*/
2011-10-18 18:53:51 +04:00
seq = mmp_new_seq ( ) ;
mmp - > mmp_seq = cpu_to_le32 ( seq ) ;
2011-05-25 02:31:25 +04:00
2012-04-30 02:47:10 +04:00
retval = write_mmp_block ( sb , bh ) ;
2011-05-25 02:31:25 +04:00
if ( retval )
goto failed ;
/*
* wait for MMP interval and check mmp_seq .
*/
if ( schedule_timeout_interruptible ( HZ * wait_time ) ! = 0 ) {
2016-04-27 08:11:21 +03:00
ext4_warning ( sb , " MMP startup interrupted, failing mount " ) ;
2011-05-25 02:31:25 +04:00
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 ;
}
mmpd_data = kmalloc ( sizeof ( struct mmpd_data ) , GFP_KERNEL ) ;
if ( ! mmpd_data ) {
ext4_warning ( sb , " not enough memory for mmpd_data " ) ;
goto failed ;
}
mmpd_data - > sb = sb ;
mmpd_data - > bh = bh ;
/*
* Start a kernel thread to update the MMP block periodically .
*/
EXT4_SB ( sb ) - > s_mmp_tsk = kthread_run ( kmmpd , mmpd_data , " kmmpd-%s " ,
bdevname ( bh - > b_bdev ,
mmp - > mmp_bdevname ) ) ;
if ( IS_ERR ( EXT4_SB ( sb ) - > s_mmp_tsk ) ) {
EXT4_SB ( sb ) - > s_mmp_tsk = NULL ;
kfree ( mmpd_data ) ;
ext4_warning ( sb , " Unable to create kmmpd thread for %s. " ,
sb - > s_id ) ;
goto failed ;
}
return 0 ;
failed :
brelse ( bh ) ;
return 1 ;
}