/* * 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 #pragma GCC diagnostic push #ifndef CONFIG_MMP #pragma GCC diagnostic ignored "-Wunused-parameter" #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) { fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT); 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; gettimeofday(&tv, 0); srand((getpid() << 16) ^ 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; #if _BSD_SOURCE || _XOPEN_SOURCE >= 500 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename)); #else mmp_s->mmp_nodename[0] = '\0'; #endif strncpy(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 } /* * 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(2 * mmp_check_interval + 1); 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(); #if _BSD_SOURCE || _XOPEN_SOURCE >= 500 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename)); #else strcpy(mmp_s->mmp_nodename, "unknown host"); #endif strncpy(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(2 * mmp_check_interval + 1); 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)) goto mmp_error; retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); 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 } #pragma GCC diagnostic pop