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