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