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