Home | History | Annotate | Download | only in ext2fs
      1 /*
      2  * Helper functions for multiple mount protection (MMP).
      3  *
      4  * Copyright (C) 2011 Whamcloud, Inc.
      5  *
      6  * %Begin-Header%
      7  * This file may be redistributed under the terms of the GNU Library
      8  * General Public License, version 2.
      9  * %End-Header%
     10  */
     11 
     12 #ifndef _GNU_SOURCE
     13 #define _GNU_SOURCE
     14 #endif
     15 #ifndef _DEFAULT_SOURCE
     16 #define _DEFAULT_SOURCE	/* since glibc 2.20 _SVID_SOURCE is deprecated */
     17 #endif
     18 
     19 #include "config.h"
     20 
     21 #if HAVE_UNISTD_H
     22 #include <unistd.h>
     23 #endif
     24 #include <sys/time.h>
     25 
     26 #include <sys/types.h>
     27 #include <sys/stat.h>
     28 #include <fcntl.h>
     29 
     30 #include "ext2fs/ext2_fs.h"
     31 #include "ext2fs/ext2fs.h"
     32 
     33 #ifndef O_DIRECT
     34 #define O_DIRECT 0
     35 #endif
     36 
     37 #pragma GCC diagnostic push
     38 #ifndef CONFIG_MMP
     39 #pragma GCC diagnostic ignored "-Wunused-parameter"
     40 #endif
     41 
     42 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
     43 {
     44 #ifdef CONFIG_MMP
     45 	struct mmp_struct *mmp_cmp;
     46 	errcode_t retval = 0;
     47 
     48 	if ((mmp_blk <= fs->super->s_first_data_block) ||
     49 	    (mmp_blk >= ext2fs_blocks_count(fs->super)))
     50 		return EXT2_ET_MMP_BAD_BLOCK;
     51 
     52 	/* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
     53 	 * mmp_fd <= 0 is OK to validate that the fd is valid.  This opens its
     54 	 * own fd to read the MMP block to ensure that it is using O_DIRECT,
     55 	 * regardless of how the io_manager is doing reads, to avoid caching of
     56 	 * the MMP block by the io_manager or the VM.  It needs to be fresh. */
     57 	if (fs->mmp_fd <= 0) {
     58 		fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
     59 		if (fs->mmp_fd < 0) {
     60 			retval = EXT2_ET_MMP_OPEN_DIRECT;
     61 			goto out;
     62 		}
     63 	}
     64 
     65 	if (fs->mmp_cmp == NULL) {
     66 		int align = ext2fs_get_dio_alignment(fs->mmp_fd);
     67 
     68 		retval = ext2fs_get_memalign(fs->blocksize, align,
     69 					     &fs->mmp_cmp);
     70 		if (retval)
     71 			return retval;
     72 	}
     73 
     74 	if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
     75 				    SEEK_SET) !=
     76 	    mmp_blk * fs->blocksize) {
     77 		retval = EXT2_ET_LLSEEK_FAILED;
     78 		goto out;
     79 	}
     80 
     81 	if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
     82 		retval = EXT2_ET_SHORT_READ;
     83 		goto out;
     84 	}
     85 
     86 	mmp_cmp = fs->mmp_cmp;
     87 
     88 	if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
     89 	    !ext2fs_mmp_csum_verify(fs, mmp_cmp))
     90 		retval = EXT2_ET_MMP_CSUM_INVALID;
     91 
     92 #ifdef WORDS_BIGENDIAN
     93 	ext2fs_swap_mmp(mmp_cmp);
     94 #endif
     95 
     96 	if (buf != NULL && buf != fs->mmp_cmp)
     97 		memcpy(buf, fs->mmp_cmp, fs->blocksize);
     98 
     99 	if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
    100 		retval = EXT2_ET_MMP_MAGIC_INVALID;
    101 		goto out;
    102 	}
    103 
    104 out:
    105 	return retval;
    106 #else
    107 	return EXT2_ET_OP_NOT_SUPPORTED;
    108 #endif
    109 }
    110 
    111 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
    112 {
    113 #ifdef CONFIG_MMP
    114 	struct mmp_struct *mmp_s = buf;
    115 	struct timeval tv;
    116 	errcode_t retval = 0;
    117 
    118 	gettimeofday(&tv, 0);
    119 	mmp_s->mmp_time = tv.tv_sec;
    120 	fs->mmp_last_written = tv.tv_sec;
    121 
    122 	if (fs->super->s_mmp_block < fs->super->s_first_data_block ||
    123 	    fs->super->s_mmp_block > ext2fs_blocks_count(fs->super))
    124 		return EXT2_ET_MMP_BAD_BLOCK;
    125 
    126 #ifdef WORDS_BIGENDIAN
    127 	ext2fs_swap_mmp(mmp_s);
    128 #endif
    129 
    130 	retval = ext2fs_mmp_csum_set(fs, mmp_s);
    131 	if (retval)
    132 		return retval;
    133 
    134 	/* I was tempted to make this use O_DIRECT and the mmp_fd, but
    135 	 * this caused no end of grief, while leaving it as-is works. */
    136 	retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
    137 
    138 #ifdef WORDS_BIGENDIAN
    139 	ext2fs_swap_mmp(mmp_s);
    140 #endif
    141 
    142 	/* Make sure the block gets to disk quickly */
    143 	io_channel_flush(fs->io);
    144 	return retval;
    145 #else
    146 	return EXT2_ET_OP_NOT_SUPPORTED;
    147 #endif
    148 }
    149 
    150 #ifdef HAVE_SRANDOM
    151 #define srand(x)	srandom(x)
    152 #define rand()		random()
    153 #endif
    154 
    155 unsigned ext2fs_mmp_new_seq(void)
    156 {
    157 #ifdef CONFIG_MMP
    158 	unsigned new_seq;
    159 	struct timeval tv;
    160 
    161 	gettimeofday(&tv, 0);
    162 	srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
    163 
    164 	gettimeofday(&tv, 0);
    165 	/* Crank the random number generator a few times */
    166 	for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
    167 		rand();
    168 
    169 	do {
    170 		new_seq = rand();
    171 	} while (new_seq > EXT4_MMP_SEQ_MAX);
    172 
    173 	return new_seq;
    174 #else
    175 	return EXT2_ET_OP_NOT_SUPPORTED;
    176 #endif
    177 }
    178 
    179 #ifdef CONFIG_MMP
    180 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
    181 {
    182 	struct mmp_struct *mmp_s = NULL;
    183 	errcode_t retval = 0;
    184 
    185 	if (fs->mmp_buf == NULL) {
    186 		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
    187 		if (retval)
    188 			goto out;
    189 	}
    190 
    191 	memset(fs->mmp_buf, 0, fs->blocksize);
    192 	mmp_s = fs->mmp_buf;
    193 
    194 	mmp_s->mmp_magic = EXT4_MMP_MAGIC;
    195 	mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
    196 	mmp_s->mmp_time = 0;
    197 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
    198 	gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
    199 #else
    200 	mmp_s->mmp_nodename[0] = '\0';
    201 #endif
    202 	strncpy(mmp_s->mmp_bdevname, fs->device_name,
    203 		sizeof(mmp_s->mmp_bdevname));
    204 
    205 	mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
    206 	if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
    207 		mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
    208 
    209 	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
    210 out:
    211 	return retval;
    212 }
    213 #endif
    214 
    215 errcode_t ext2fs_mmp_update(ext2_filsys fs)
    216 {
    217 	return ext2fs_mmp_update2(fs, 0);
    218 }
    219 
    220 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
    221 {
    222 #ifdef CONFIG_MMP
    223 	errcode_t retval = 0;
    224 
    225 	if (!(fs->flags & EXT2_FLAG_RW))
    226 		return EXT2_ET_RO_FILSYS;
    227 
    228 	retval = ext2fs_mmp_reset(fs);
    229 
    230 	return retval;
    231 #else
    232 	return EXT2_ET_OP_NOT_SUPPORTED;
    233 #endif
    234 }
    235 
    236 errcode_t ext2fs_mmp_init(ext2_filsys fs)
    237 {
    238 #ifdef CONFIG_MMP
    239 	struct ext2_super_block *sb = fs->super;
    240 	blk64_t mmp_block;
    241 	errcode_t retval;
    242 
    243 	if (sb->s_mmp_update_interval == 0)
    244 		sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
    245 	/* This is probably excessively large, but who knows? */
    246 	else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
    247 		return EXT2_ET_INVALID_ARGUMENT;
    248 
    249 	if (fs->mmp_buf == NULL) {
    250 		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
    251 		if (retval)
    252 			goto out;
    253 	}
    254 
    255 	retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
    256 	if (retval)
    257 		goto out;
    258 
    259 	sb->s_mmp_block = mmp_block;
    260 
    261 	retval = ext2fs_mmp_reset(fs);
    262 	if (retval)
    263 		goto out;
    264 
    265 out:
    266 	return retval;
    267 #else
    268 	return EXT2_ET_OP_NOT_SUPPORTED;
    269 #endif
    270 }
    271 
    272 /*
    273  * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
    274  */
    275 errcode_t ext2fs_mmp_start(ext2_filsys fs)
    276 {
    277 #ifdef CONFIG_MMP
    278 	struct mmp_struct *mmp_s;
    279 	unsigned seq;
    280 	unsigned int mmp_check_interval;
    281 	errcode_t retval = 0;
    282 
    283 	if (fs->mmp_buf == NULL) {
    284 		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
    285 		if (retval)
    286 			goto mmp_error;
    287 	}
    288 
    289 	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
    290 	if (retval)
    291 		goto mmp_error;
    292 
    293 	mmp_s = fs->mmp_buf;
    294 
    295 	mmp_check_interval = fs->super->s_mmp_update_interval;
    296 	if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
    297 		mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
    298 
    299 	seq = mmp_s->mmp_seq;
    300 	if (seq == EXT4_MMP_SEQ_CLEAN)
    301 		goto clean_seq;
    302 	if (seq == EXT4_MMP_SEQ_FSCK) {
    303 		retval = EXT2_ET_MMP_FSCK_ON;
    304 		goto mmp_error;
    305 	}
    306 
    307 	if (seq > EXT4_MMP_SEQ_FSCK) {
    308 		retval = EXT2_ET_MMP_UNKNOWN_SEQ;
    309 		goto mmp_error;
    310 	}
    311 
    312 	/*
    313 	 * If check_interval in MMP block is larger, use that instead of
    314 	 * check_interval from the superblock.
    315 	 */
    316 	if (mmp_s->mmp_check_interval > mmp_check_interval)
    317 		mmp_check_interval = mmp_s->mmp_check_interval;
    318 
    319 	sleep(2 * mmp_check_interval + 1);
    320 
    321 	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
    322 	if (retval)
    323 		goto mmp_error;
    324 
    325 	if (seq != mmp_s->mmp_seq) {
    326 		retval = EXT2_ET_MMP_FAILED;
    327 		goto mmp_error;
    328 	}
    329 
    330 clean_seq:
    331 	if (!(fs->flags & EXT2_FLAG_RW))
    332 		goto mmp_error;
    333 
    334 	mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
    335 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
    336 	gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
    337 #else
    338 	strcpy(mmp_s->mmp_nodename, "unknown host");
    339 #endif
    340 	strncpy(mmp_s->mmp_bdevname, fs->device_name,
    341 		sizeof(mmp_s->mmp_bdevname));
    342 
    343 	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
    344 	if (retval)
    345 		goto mmp_error;
    346 
    347 	sleep(2 * mmp_check_interval + 1);
    348 
    349 	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
    350 	if (retval)
    351 		goto mmp_error;
    352 
    353 	if (seq != mmp_s->mmp_seq) {
    354 		retval = EXT2_ET_MMP_FAILED;
    355 		goto mmp_error;
    356 	}
    357 
    358 	mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
    359 	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
    360 	if (retval)
    361 		goto mmp_error;
    362 
    363 	return 0;
    364 
    365 mmp_error:
    366 	return retval;
    367 #else
    368 	return EXT2_ET_OP_NOT_SUPPORTED;
    369 #endif
    370 }
    371 
    372 /*
    373  * Clear the MMP usage in the filesystem.  If this function returns an
    374  * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
    375  * by some other process while in use, and changes should be dropped, or
    376  * risk filesystem corruption.
    377  */
    378 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
    379 {
    380 #ifdef CONFIG_MMP
    381 	struct mmp_struct *mmp, *mmp_cmp;
    382 	errcode_t retval = 0;
    383 
    384 	if (!ext2fs_has_feature_mmp(fs->super) ||
    385 	    !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
    386 		goto mmp_error;
    387 
    388 	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
    389 	if (retval)
    390 		goto mmp_error;
    391 
    392 	/* Check if the MMP block is not changed. */
    393 	mmp = fs->mmp_buf;
    394 	mmp_cmp = fs->mmp_cmp;
    395 	if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
    396 		retval = EXT2_ET_MMP_CHANGE_ABORT;
    397 		goto mmp_error;
    398 	}
    399 
    400 	mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
    401 	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
    402 
    403 mmp_error:
    404 	if (fs->mmp_fd > 0) {
    405 		close(fs->mmp_fd);
    406 		fs->mmp_fd = -1;
    407 	}
    408 
    409 	return retval;
    410 #else
    411 	if (!ext2fs_has_feature_mmp(fs->super) ||
    412 	    !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
    413 		return 0;
    414 
    415 	return EXT2_ET_OP_NOT_SUPPORTED;
    416 #endif
    417 }
    418 
    419 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
    420 
    421 /*
    422  * Update the on-disk mmp buffer, after checking that it hasn't been changed.
    423  */
    424 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
    425 {
    426 #ifdef CONFIG_MMP
    427 	struct mmp_struct *mmp, *mmp_cmp;
    428 	struct timeval tv;
    429 	errcode_t retval = 0;
    430 
    431 	if (!ext2fs_has_feature_mmp(fs->super) ||
    432 	    !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
    433 		return 0;
    434 
    435 	gettimeofday(&tv, 0);
    436 	if (!immediately &&
    437 	    tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
    438 		return 0;
    439 
    440 	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
    441 	if (retval)
    442 		goto mmp_error;
    443 
    444 	mmp = fs->mmp_buf;
    445 	mmp_cmp = fs->mmp_cmp;
    446 
    447 	if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
    448 		return EXT2_ET_MMP_CHANGE_ABORT;
    449 
    450 	mmp->mmp_time = tv.tv_sec;
    451 	mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
    452 	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
    453 
    454 mmp_error:
    455 	return retval;
    456 #else
    457 	if (!ext2fs_has_feature_mmp(fs->super) ||
    458 	    !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
    459 		return 0;
    460 
    461 	return EXT2_ET_OP_NOT_SUPPORTED;
    462 #endif
    463 }
    464 #pragma GCC diagnostic pop
    465