Re: [RFC] ext3 multi-mount protection
Qi Yong <[email protected]>
| Newsgroups | gmane.comp.file-systems.ext2.devel |
|---|---|
| Organization | CFS |
| Message-ID | <[email protected]> |
Andreas Dilger wrote:
> Is there any ext3/ext4 interest in a mechanism for protecting ext3/ext4
> filesystems from being mounted concurrently by multiple nodes on a SAN
> at the same time?
>
> We often run the ext3 server filesystems in an HA configuration so that
> there are two servers on a FC switch that can access the same shared
> disk. Sadly, HA software is far from reliable and this can sometimes
> lead to both servers trying to access the same device at one time.
> This is true if you are using ext3 for HA NFS serving, or other non-HA
> setups where the same disks are accessible by multiple nodes (regardless
> of whether they are _supposed_ to touch those disks or not), because the
> ext3 journal recovery can corrupt the filesystem even if mounted read-only,
> or if some clueless sysadmin tries to mount or use "those unused disks".
>
> We've developed a small patch for ext3 to prevent the filesystem from
> being mounted concurrently on two nodes. Is there general interest in
> this becoming part of ext3/ext4?
>
> Cheers, Andreas
> --
> Andreas Dilger
> Principal Software Engineer
> Cluster File Systems, Inc.
Hello,
The attached is our mmp v3.0 kernel patch against the current vanilla kernel in git.
(mmp: multi-mount protection)
tytso, could you possibly reserve this flag?
#define EXT3_FEATURE_INCOMPAT_ALIVE 0x0080
Rationale
---------
An incompatible feature "alive" is introduced to enable mmp.
With feature alive set:
On an active fs, kalived updates "sequence number" in "alive block" at 5-second intervals.
At mount time, the alive block is read and tested to detect if the mounting fs is actively used
by other nodes or not. If the fs is active, mount fails; otherwise start kalived and continue.
Algorithm
---------
1 read alive_struct
2 check sequence number == 0? yes, goto 6
3 wait 8s
4 reread alive_struct
5 seq change? yes = exit
6 write new random sequence number
7 wait 6s
8 reread alive_struct
9 seq change? yes = exit
10 start kalived
alive block
-----------
#define ALIVE_MAGIC 0xA1153C29
struct alive_struct {
__le32 al_magic;
__le32 al_seq;
__le32 al_time;
char al_nodename[65];
};
In v1, the alive block resides in the unused sector #0.
In v2, the alive block resides in a hidden inode.
In v3, the alive block resides in a regular file "/.alive".
--
Qi Yong
System Software Engineer
Cluster File Systems, Inc.
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Ext2-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ext2-devel
mmp-v3.0.patch
(text/x-patch, 10.1 KB)
Signed-off-by: Qi Yong <[email protected]> --- fs/ext3/al.h | 11 + fs/ext3/namei.c | 2 fs/ext3/super.c | 262 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/ext3_fs.h | 4 include/linux/ext3_fs_sb.h | 1 5 files changed, 278 insertions(+), 2 deletions(-) diff --git a/fs/ext3/al.h b/fs/ext3/al.h new file mode 100644 index 0000000..88b9598 --- /dev/null +++ b/fs/ext3/al.h @@ -0,0 +1,11 @@ +/* + * (C) 2006 Qi Yong <[email protected]> + */ + +#define ALIVE_MAGIC 0xA1153C29 +struct alive_struct { + __le32 al_magic; + __le32 al_seq; + __le32 al_time; + char al_nodename[65]; +}; diff --git a/fs/ext3/namei.c b/fs/ext3/namei.c index d9176db..4201a8f 100644 --- a/fs/ext3/namei.c +++ b/fs/ext3/namei.c @@ -804,7 +804,7 @@ static inline int search_dirblock(struct * The returned buffer_head has ->b_count elevated. The caller is expected * to brelse() it when appropriate. */ -static struct buffer_head * ext3_find_entry (struct dentry *dentry, +struct buffer_head * ext3_find_entry (struct dentry *dentry, struct ext3_dir_entry_2 ** res_dir) { struct super_block * sb; diff --git a/fs/ext3/super.c b/fs/ext3/super.c index 813d589..042b5ab 100644 --- a/fs/ext3/super.c +++ b/fs/ext3/super.c @@ -16,6 +16,7 @@ * David S. Miller ([email protected]), 1995 */ +#define DEBUG #include <linux/module.h> #include <linux/string.h> #include <linux/fs.h> @@ -35,12 +36,14 @@ #include <linux/mount.h> #include <linux/namei.h> #include <linux/quotaops.h> #include <linux/seq_file.h> +#include <linux/kthread.h> #include <asm/uaccess.h> #include "xattr.h" #include "acl.h" #include "namei.h" +#include "al.h" static int ext3_load_journal(struct super_block *, struct ext3_super_block *, unsigned long journal_devnum); @@ -61,6 +64,8 @@ static int ext3_statfs (struct dentry * static void ext3_unlockfs(struct super_block *sb); static void ext3_write_super (struct super_block * sb); static void ext3_write_super_lockfs(struct super_block *sb); +struct buffer_head * ext3_find_entry (struct dentry *dentry, + struct ext3_dir_entry_2 ** res_dir); /* * Wrappers for journal_start/end. @@ -430,6 +435,10 @@ #endif invalidate_bdev(sbi->journal_bdev, 0); ext3_blkdev_remove(sbi); } + if (EXT3_HAS_INCOMPAT_FEATURE(sb, EXT3_FEATURE_INCOMPAT_ALIVE)) { + BUG_ON(!sbi->s_alive_tsk); + kthread_stop(sbi->s_alive_tsk); + } sb->s_fs_info = NULL; kfree(sbi); return; @@ -1338,6 +1347,255 @@ static ext3_fsblk_t descriptor_loc(struc return (has_super + ext3_group_first_block_no(sb, bg)); } +static int write_alive(struct buffer_head * bh) +{ + lock_buffer(bh); + bh->b_end_io = end_buffer_write_sync; + get_bh(bh); + submit_bh(WRITE, bh); + wait_on_buffer(bh); + if (unlikely(!buffer_uptodate(bh))) + return 1; + return 0; +} + +static int read_alive_again(struct buffer_head * bh) +{ + lock_buffer(bh); + bh->b_end_io = end_buffer_read_sync; + get_bh(bh); + submit_bh(READ, bh); + wait_on_buffer(bh); + if (!buffer_uptodate(bh)) { + brelse(bh); + return 1; + } + return 0; +} + +/* + * The caller must have a ref on the buffer_head. + */ +static int kalived(void *data) +{ + struct buffer_head * bh; + struct alive_struct * alive; + char b[BDEVNAME_SIZE]; + u32 seq = 0; + + bh = (struct buffer_head *)data; + bdevname(bh->b_bdev, b); + + alive = (struct alive_struct *)(bh->b_data); + alive->al_magic = cpu_to_le32(ALIVE_MAGIC); + alive->al_time = cpu_to_le32(get_seconds()); + + down_read(&uts_sem); + memcpy(alive->al_nodename, system_utsname.nodename, 65); + up_read(&uts_sem); + + while (!kthread_should_stop()) { + if (++seq == 0) + ++seq; + + alive->al_seq = cpu_to_le32(seq); + alive->al_time = cpu_to_le32(get_seconds()); + + if (unlikely(write_alive(bh))) { + /* panic here? */ + printk(KERN_ERR "Alive (device %s): " + "can't write alive block\n", b); + continue; + } + + schedule_timeout_interruptible(5 * HZ); + } + + alive->al_seq = 0; + alive->al_time = cpu_to_le32(get_seconds()); + + if (unlikely(write_alive(bh))) + printk(KERN_ERR "Alive (device %s): " + "can't reset alive block\n", b); + brelse(bh); + return 0; +} + +static unsigned long get_alive_ino(struct super_block *sb) +{ + unsigned long ino = 0; + struct dentry alive; + struct dentry * root; + struct inode * root_inode; + struct ext3_dir_entry_2 * de; + struct buffer_head * bh; + + root_inode = iget(sb, EXT3_ROOT_INO); + root = d_alloc_root(root_inode); + if (!root) { + printk(KERN_ERR "Alive (device %s): get root inode failed\n", + sb->s_id); + iput(root_inode); + goto out; + } + + alive.d_name.name = ".alive"; + alive.d_name.len = 6; + alive.d_parent = root; + + bh = ext3_find_entry(&alive, &de); + dput(root); + + if (!bh) { + printk(KERN_WARNING "Alive (device %s): alive lookup failed\n", + sb->s_id); + goto out; + } + + ino = le32_to_cpu(de->inode); + brelse (bh); + pr_debug("Alive (device %s): alive_ino=%lu\n", sb->s_id, ino); +out: + return ino; +} + +/* check alive file */ +static int check_alive(struct super_block *sb, struct ext3_sb_info *sbi) +{ + unsigned long ino; + struct buffer_head * bh; + struct ext3_inode_info * ei; + struct inode * alive_inode; + struct alive_struct * alive; + u32 alive_block; + u32 seq; + + ino = get_alive_ino(sb); + if (!ino) + goto failed; + + alive_inode = iget(sb, ino); + if (!alive_inode) { + iput(alive_inode); + printk(KERN_ERR "Alive (device %s): get alive inode failed\n", + sb->s_id); + goto failed; + } + if (!alive_inode->i_nlink) { + make_bad_inode(alive_inode); + iput(alive_inode); + printk(KERN_ERR "Alive (device %s): alive inode is deleted\n", + sb->s_id); + goto failed; + } + if (!S_ISREG(alive_inode->i_mode)) { + iput(alive_inode); + printk(KERN_ERR "Alive (device %s): invalid alive inode\n", + sb->s_id); + goto failed; + } + + ei = EXT3_I(alive_inode); + alive_block = ei->i_data[0]; + iput(alive_inode); + + pr_debug("Alive (device %s): read in alive block #%u\n", + sb->s_id, alive_block); + + /* first read */ + bh = sb_bread(sb, alive_block); + if (!bh) { + printk(KERN_ERR "Alive (device %s): " + "can't read alive block #%u\n", sb->s_id, alive_block); + goto failed; + } + + alive = (struct alive_struct *)(bh->b_data); + if (le32_to_cpu(alive->al_magic) != ALIVE_MAGIC) { + printk(KERN_ERR "Alive (device %s): " + "magic mismatch\n", sb->s_id); + brelse(bh); + goto failed; + } + + seq = le32_to_cpu(alive->al_seq); + pr_debug("Alive (device %s): seq=%u\n", sb->s_id, seq); + pr_info ("Alive (device %s): last touched by node: %s, " + "%li seconds ago\n", sb->s_id, alive->al_nodename, + get_seconds() - le32_to_cpu(alive->al_time)); + + if (seq == 0) + goto skip; + + /* wait 8s */ + pr_info("Alive (device %s): wait for 8 seconds...\n", sb->s_id); + schedule_timeout_uninterruptible(HZ * 8); + + /* read again */ + if (read_alive_again(bh)) { + printk(KERN_ERR "Alive (device %s): " + "can't read alive block #%u\n", + sb->s_id, alive_block); + goto failed; + } + + alive = (struct alive_struct *)(bh->b_data); + pr_debug("Alive (device %s): seq=%u\n", + sb->s_id, le32_to_cpu(alive->al_seq)); + + if (seq != le32_to_cpu(alive->al_seq)) { + printk(KERN_WARNING "Alive (device %s): " + "still active on node %s\n", + sb->s_id, alive->al_nodename); + brelse(bh); + goto failed; + } +skip: + /* write a new random seq */ + get_random_bytes(&seq, sizeof(u32)); + alive->al_seq = cpu_to_le32(seq); + if (unlikely(write_alive(bh))) { + printk(KERN_ERR "Alive (device %s): " + "can't write alive block\n", sb->s_id); + goto failed; + } + pr_debug("Alive (device %s): write random seq=%u\n", sb->s_id, seq); + + /* wait 6s */ + pr_info("Alive (device %s): wait for 6 seconds...\n", sb->s_id); + schedule_timeout_uninterruptible(HZ * 6); + + /* read again */ + if (read_alive_again(bh)) { + printk(KERN_ERR "Alive (device %s): " + "can't read alive block #%u\n", + sb->s_id, alive_block); + goto failed; + } + + alive = (struct alive_struct *)(bh->b_data); + pr_debug("Alive (device %s): seq=%u\n", + sb->s_id, le32_to_cpu(alive->al_seq)); + + if (seq != le32_to_cpu(alive->al_seq)) { + printk(KERN_WARNING "Alive (device %s): " + "still active on node %s\n", + sb->s_id, alive->al_nodename); + brelse(bh); + goto failed; + } + + /* succeed */ + pr_info("Alive (device %s): alive check passed!\n", sb->s_id); + sbi->s_alive_tsk = kthread_run(kalived, bh, "kalived"); + return 0; + +failed: + printk(KERN_WARNING "Alive (device %s): alive check failed!\n", + sb->s_id); + return 1; +} + static int ext3_fill_super (struct super_block *sb, void *data, int silent) { @@ -1651,6 +1909,10 @@ #endif EXT3_HAS_INCOMPAT_FEATURE(sb, EXT3_FEATURE_INCOMPAT_RECOVER)); + if (EXT3_HAS_INCOMPAT_FEATURE(sb, EXT3_FEATURE_INCOMPAT_ALIVE)) + if (check_alive(sb, sbi)) + goto failed_mount2; + /* * The first inode we look at is the journal inode. Don't try * root first: it may be modified in the journal! diff --git a/include/linux/ext3_fs.h b/include/linux/ext3_fs.h index 5607e64..e2293d0 100644 --- a/include/linux/ext3_fs.h +++ b/include/linux/ext3_fs.h @@ -560,11 +560,13 @@ #define EXT3_FEATURE_INCOMPAT_FILETYPE #define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Needs recovery */ #define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Journal device */ #define EXT3_FEATURE_INCOMPAT_META_BG 0x0010 +#define EXT3_FEATURE_INCOMPAT_ALIVE 0x0080 #define EXT3_FEATURE_COMPAT_SUPP EXT2_FEATURE_COMPAT_EXT_ATTR #define EXT3_FEATURE_INCOMPAT_SUPP (EXT3_FEATURE_INCOMPAT_FILETYPE| \ EXT3_FEATURE_INCOMPAT_RECOVER| \ - EXT3_FEATURE_INCOMPAT_META_BG) + EXT3_FEATURE_INCOMPAT_META_BG| \ + EXT3_FEATURE_INCOMPAT_ALIVE) #define EXT3_FEATURE_RO_COMPAT_SUPP (EXT3_FEATURE_RO_COMPAT_SPARSE_SUPER| \ EXT3_FEATURE_RO_COMPAT_LARGE_FILE| \ EXT3_FEATURE_RO_COMPAT_BTREE_DIR) diff --git a/include/linux/ext3_fs_sb.h b/include/linux/ext3_fs_sb.h index f61309c..43627b3 100644 --- a/include/linux/ext3_fs_sb.h +++ b/include/linux/ext3_fs_sb.h @@ -78,6 +78,7 @@ #ifdef CONFIG_QUOTA char *s_qf_names[MAXQUOTAS]; /* Names of quota files with journalled quota */ int s_jquota_fmt; /* Format of quota to use */ #endif + struct task_struct * s_alive_tsk; }; #endif /* _LINUX_EXT3_FS_SB */