diff options
Diffstat (limited to 'lib/ext2fs/mmp.c')
-rw-r--r-- | lib/ext2fs/mmp.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/lib/ext2fs/mmp.c b/lib/ext2fs/mmp.c new file mode 100644 index 0000000..eb94170 --- /dev/null +++ b/lib/ext2fs/mmp.c @@ -0,0 +1,488 @@ +/* + * Helper functions for multiple mount protection (MMP). + * + * Copyright (C) 2011 Whamcloud, Inc. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Library + * General Public License, version 2. + * %End-Header% + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE /* since glibc 2.20 _SVID_SOURCE is deprecated */ +#endif + +#include "config.h" + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sys/time.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" + +#ifndef O_DIRECT +#define O_DIRECT 0 +#endif + +#if __GNUC_PREREQ (4, 6) +#pragma GCC diagnostic push +#ifndef CONFIG_MMP +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif +#endif + +errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf) +{ +#ifdef CONFIG_MMP + struct mmp_struct *mmp_cmp; + errcode_t retval = 0; + + if ((mmp_blk <= fs->super->s_first_data_block) || + (mmp_blk >= ext2fs_blocks_count(fs->super))) + return EXT2_ET_MMP_BAD_BLOCK; + + /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking + * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its + * own fd to read the MMP block to ensure that it is using O_DIRECT, + * regardless of how the io_manager is doing reads, to avoid caching of + * the MMP block by the io_manager or the VM. It needs to be fresh. */ + if (fs->mmp_fd <= 0) { + struct stat st; + int flags = O_RDONLY | O_DIRECT; + + /* + * There is no reason for using O_DIRECT if we're working with + * regular file. Disabling it also avoids problems with + * alignment when the device of the host file system has sector + * size larger than blocksize of the fs we're working with. + */ + if (stat(fs->device_name, &st) == 0 && + S_ISREG(st.st_mode)) + flags &= ~O_DIRECT; + + fs->mmp_fd = open(fs->device_name, flags); + if (fs->mmp_fd < 0) { + retval = EXT2_ET_MMP_OPEN_DIRECT; + goto out; + } + } + + if (fs->mmp_cmp == NULL) { + int align = ext2fs_get_dio_alignment(fs->mmp_fd); + + retval = ext2fs_get_memalign(fs->blocksize, align, + &fs->mmp_cmp); + if (retval) + return retval; + } + + if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize, + SEEK_SET) != + mmp_blk * fs->blocksize) { + retval = EXT2_ET_LLSEEK_FAILED; + goto out; + } + + if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) { + retval = EXT2_ET_SHORT_READ; + goto out; + } + + mmp_cmp = fs->mmp_cmp; + + if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) && + !ext2fs_mmp_csum_verify(fs, mmp_cmp)) + retval = EXT2_ET_MMP_CSUM_INVALID; + +#ifdef WORDS_BIGENDIAN + ext2fs_swap_mmp(mmp_cmp); +#endif + + if (buf != NULL && buf != fs->mmp_cmp) + memcpy(buf, fs->mmp_cmp, fs->blocksize); + + if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) { + retval = EXT2_ET_MMP_MAGIC_INVALID; + goto out; + } + +out: + return retval; +#else + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf) +{ +#ifdef CONFIG_MMP + struct mmp_struct *mmp_s = buf; + struct timeval tv; + errcode_t retval = 0; + + gettimeofday(&tv, 0); + mmp_s->mmp_time = tv.tv_sec; + fs->mmp_last_written = tv.tv_sec; + + if (fs->super->s_mmp_block < fs->super->s_first_data_block || + fs->super->s_mmp_block > ext2fs_blocks_count(fs->super)) + return EXT2_ET_MMP_BAD_BLOCK; + +#ifdef WORDS_BIGENDIAN + ext2fs_swap_mmp(mmp_s); +#endif + + retval = ext2fs_mmp_csum_set(fs, mmp_s); + if (retval) + return retval; + + /* I was tempted to make this use O_DIRECT and the mmp_fd, but + * this caused no end of grief, while leaving it as-is works. */ + retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf); + +#ifdef WORDS_BIGENDIAN + ext2fs_swap_mmp(mmp_s); +#endif + + /* Make sure the block gets to disk quickly */ + io_channel_flush(fs->io); + return retval; +#else + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +#ifdef HAVE_SRANDOM +#define srand(x) srandom(x) +#define rand() random() +#endif + +unsigned ext2fs_mmp_new_seq(void) +{ +#ifdef CONFIG_MMP + unsigned new_seq; + struct timeval tv; + unsigned long pid = getpid(); + + gettimeofday(&tv, 0); + pid = (pid >> 16) | ((pid & 0xFFFF) << 16); + srand(pid ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); + + gettimeofday(&tv, 0); + /* Crank the random number generator a few times */ + for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--) + rand(); + + do { + new_seq = rand(); + } while (new_seq > EXT4_MMP_SEQ_MAX); + + return new_seq; +#else + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_MMP +static errcode_t ext2fs_mmp_reset(ext2_filsys fs) +{ + struct mmp_struct *mmp_s = NULL; + errcode_t retval = 0; + + if (fs->mmp_buf == NULL) { + retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf); + if (retval) + goto out; + } + + memset(fs->mmp_buf, 0, fs->blocksize); + mmp_s = fs->mmp_buf; + + mmp_s->mmp_magic = EXT4_MMP_MAGIC; + mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN; + mmp_s->mmp_time = 0; +#ifdef HAVE_GETHOSTNAME + gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename)); +#else + mmp_s->mmp_nodename[0] = '\0'; +#endif + strncpy((char *) mmp_s->mmp_bdevname, fs->device_name, + sizeof(mmp_s->mmp_bdevname)); + + mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval; + if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL) + mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL; + + retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); +out: + return retval; +} +#endif + +errcode_t ext2fs_mmp_update(ext2_filsys fs) +{ + return ext2fs_mmp_update2(fs, 0); +} + +errcode_t ext2fs_mmp_clear(ext2_filsys fs) +{ +#ifdef CONFIG_MMP + errcode_t retval = 0; + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + retval = ext2fs_mmp_reset(fs); + + return retval; +#else + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +errcode_t ext2fs_mmp_init(ext2_filsys fs) +{ +#ifdef CONFIG_MMP + struct ext2_super_block *sb = fs->super; + blk64_t mmp_block; + errcode_t retval; + + if (sb->s_mmp_update_interval == 0) + sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL; + /* This is probably excessively large, but who knows? */ + else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL) + return EXT2_ET_INVALID_ARGUMENT; + + if (fs->mmp_buf == NULL) { + retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf); + if (retval) + goto out; + } + + retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block); + if (retval) + goto out; + + sb->s_mmp_block = mmp_block; + + retval = ext2fs_mmp_reset(fs); + if (retval) + goto out; + +out: + return retval; +#else + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +#ifndef min +#define min(x, y) ((x) < (y) ? (x) : (y)) +#endif + +/* + * Make sure that the fs is not mounted or being fsck'ed while opening the fs. + */ +errcode_t ext2fs_mmp_start(ext2_filsys fs) +{ +#ifdef CONFIG_MMP + struct mmp_struct *mmp_s; + unsigned seq; + unsigned int mmp_check_interval; + errcode_t retval = 0; + + if (fs->mmp_buf == NULL) { + retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf); + if (retval) + goto mmp_error; + } + + retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); + if (retval) + goto mmp_error; + + mmp_s = fs->mmp_buf; + + mmp_check_interval = fs->super->s_mmp_update_interval; + if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL) + mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL; + + seq = mmp_s->mmp_seq; + if (seq == EXT4_MMP_SEQ_CLEAN) + goto clean_seq; + if (seq == EXT4_MMP_SEQ_FSCK) { + retval = EXT2_ET_MMP_FSCK_ON; + goto mmp_error; + } + + if (seq > EXT4_MMP_SEQ_FSCK) { + retval = EXT2_ET_MMP_UNKNOWN_SEQ; + goto mmp_error; + } + + /* + * If check_interval in MMP block is larger, use that instead of + * check_interval from the superblock. + */ + if (mmp_s->mmp_check_interval > mmp_check_interval) + mmp_check_interval = mmp_s->mmp_check_interval; + + sleep(min(mmp_check_interval * 2 + 1, mmp_check_interval + 60)); + + retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); + if (retval) + goto mmp_error; + + if (seq != mmp_s->mmp_seq) { + retval = EXT2_ET_MMP_FAILED; + goto mmp_error; + } + +clean_seq: + if (!(fs->flags & EXT2_FLAG_RW)) + goto mmp_error; + + mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq(); +#ifdef HAVE_GETHOSTNAME + gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename)); +#else + strcpy((char *) mmp_s->mmp_nodename, "unknown host"); +#endif + strncpy((char *) mmp_s->mmp_bdevname, fs->device_name, + sizeof(mmp_s->mmp_bdevname)); + + retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); + if (retval) + goto mmp_error; + + sleep(min(2 * mmp_check_interval + 1, mmp_check_interval + 60)); + + retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); + if (retval) + goto mmp_error; + + if (seq != mmp_s->mmp_seq) { + retval = EXT2_ET_MMP_FAILED; + goto mmp_error; + } + + mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK; + retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); + if (retval) + goto mmp_error; + + return 0; + +mmp_error: + return retval; +#else + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +/* + * Clear the MMP usage in the filesystem. If this function returns an + * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified + * by some other process while in use, and changes should be dropped, or + * risk filesystem corruption. + */ +errcode_t ext2fs_mmp_stop(ext2_filsys fs) +{ +#ifdef CONFIG_MMP + struct mmp_struct *mmp, *mmp_cmp; + errcode_t retval = 0; + + if (!ext2fs_has_feature_mmp(fs->super) || + !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP) || + (fs->mmp_buf == NULL) || (fs->mmp_cmp == NULL)) + goto mmp_error; + + retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL); + if (retval) + goto mmp_error; + + /* Check if the MMP block is not changed. */ + mmp = fs->mmp_buf; + mmp_cmp = fs->mmp_cmp; + if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) { + retval = EXT2_ET_MMP_CHANGE_ABORT; + goto mmp_error; + } + + mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN; + retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp); + +mmp_error: + if (fs->mmp_fd > 0) { + close(fs->mmp_fd); + fs->mmp_fd = -1; + } + + return retval; +#else + if (!ext2fs_has_feature_mmp(fs->super) || + !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP)) + return 0; + + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} + +#define EXT2_MIN_MMP_UPDATE_INTERVAL 60 + +/* + * Update the on-disk mmp buffer, after checking that it hasn't been changed. + */ +errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately) +{ +#ifdef CONFIG_MMP + struct mmp_struct *mmp, *mmp_cmp; + struct timeval tv; + errcode_t retval = 0; + + if (!ext2fs_has_feature_mmp(fs->super) || + !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP)) + return 0; + + gettimeofday(&tv, 0); + if (!immediately && + tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL) + return 0; + + retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL); + if (retval) + goto mmp_error; + + mmp = fs->mmp_buf; + mmp_cmp = fs->mmp_cmp; + + if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) + return EXT2_ET_MMP_CHANGE_ABORT; + + mmp->mmp_time = tv.tv_sec; + mmp->mmp_seq = EXT4_MMP_SEQ_FSCK; + retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); + +mmp_error: + return retval; +#else + if (!ext2fs_has_feature_mmp(fs->super) || + !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP)) + return 0; + + return EXT2_ET_OP_NOT_SUPPORTED; +#endif +} +#if __GNUC_PREREQ (4, 6) +#pragma GCC diagnostic pop +#endif |