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