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