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 42errcode_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 104out: 105 return retval; 106#else 107 return EXT2_ET_OP_NOT_SUPPORTED; 108#endif 109} 110 111errcode_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 155unsigned 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 180static 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); 210out: 211 return retval; 212} 213#endif 214 215errcode_t ext2fs_mmp_update(ext2_filsys fs) 216{ 217 return ext2fs_mmp_update2(fs, 0); 218} 219 220errcode_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 236errcode_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 265out: 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 */ 275errcode_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 330clean_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 365mmp_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 */ 378errcode_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 403mmp_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 */ 424errcode_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 454mmp_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