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 struct stat st; 61 int flags = O_RDWR | O_DIRECT; 62 63 /* 64 * There is no reason for using O_DIRECT if we're working with 65 * regular file. Disabling it also avoids problems with 66 * alignment when the device of the host file system has sector 67 * size larger than blocksize of the fs we're working with. 68 */ 69 if (stat(fs->device_name, &st) == 0 && 70 S_ISREG(st.st_mode)) 71 flags &= ~O_DIRECT; 72 73 fs->mmp_fd = open(fs->device_name, flags); 74 if (fs->mmp_fd < 0) { 75 retval = EXT2_ET_MMP_OPEN_DIRECT; 76 goto out; 77 } 78 } 79 80 if (fs->mmp_cmp == NULL) { 81 int align = ext2fs_get_dio_alignment(fs->mmp_fd); 82 83 retval = ext2fs_get_memalign(fs->blocksize, align, 84 &fs->mmp_cmp); 85 if (retval) 86 return retval; 87 } 88 89 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize, 90 SEEK_SET) != 91 mmp_blk * fs->blocksize) { 92 retval = EXT2_ET_LLSEEK_FAILED; 93 goto out; 94 } 95 96 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) { 97 retval = EXT2_ET_SHORT_READ; 98 goto out; 99 } 100 101 mmp_cmp = fs->mmp_cmp; 102 103 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) && 104 !ext2fs_mmp_csum_verify(fs, mmp_cmp)) 105 retval = EXT2_ET_MMP_CSUM_INVALID; 106 107 #ifdef WORDS_BIGENDIAN 108 ext2fs_swap_mmp(mmp_cmp); 109 #endif 110 111 if (buf != NULL && buf != fs->mmp_cmp) 112 memcpy(buf, fs->mmp_cmp, fs->blocksize); 113 114 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) { 115 retval = EXT2_ET_MMP_MAGIC_INVALID; 116 goto out; 117 } 118 119 out: 120 return retval; 121 #else 122 return EXT2_ET_OP_NOT_SUPPORTED; 123 #endif 124 } 125 126 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf) 127 { 128 #ifdef CONFIG_MMP 129 struct mmp_struct *mmp_s = buf; 130 struct timeval tv; 131 errcode_t retval = 0; 132 133 gettimeofday(&tv, 0); 134 mmp_s->mmp_time = tv.tv_sec; 135 fs->mmp_last_written = tv.tv_sec; 136 137 if (fs->super->s_mmp_block < fs->super->s_first_data_block || 138 fs->super->s_mmp_block > ext2fs_blocks_count(fs->super)) 139 return EXT2_ET_MMP_BAD_BLOCK; 140 141 #ifdef WORDS_BIGENDIAN 142 ext2fs_swap_mmp(mmp_s); 143 #endif 144 145 retval = ext2fs_mmp_csum_set(fs, mmp_s); 146 if (retval) 147 return retval; 148 149 /* I was tempted to make this use O_DIRECT and the mmp_fd, but 150 * this caused no end of grief, while leaving it as-is works. */ 151 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf); 152 153 #ifdef WORDS_BIGENDIAN 154 ext2fs_swap_mmp(mmp_s); 155 #endif 156 157 /* Make sure the block gets to disk quickly */ 158 io_channel_flush(fs->io); 159 return retval; 160 #else 161 return EXT2_ET_OP_NOT_SUPPORTED; 162 #endif 163 } 164 165 #ifdef HAVE_SRANDOM 166 #define srand(x) srandom(x) 167 #define rand() random() 168 #endif 169 170 unsigned ext2fs_mmp_new_seq(void) 171 { 172 #ifdef CONFIG_MMP 173 unsigned new_seq; 174 struct timeval tv; 175 unsigned long pid = getpid(); 176 177 gettimeofday(&tv, 0); 178 pid = (pid >> 16) | ((pid & 0xFFFF) << 16); 179 srand(pid ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); 180 181 gettimeofday(&tv, 0); 182 /* Crank the random number generator a few times */ 183 for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--) 184 rand(); 185 186 do { 187 new_seq = rand(); 188 } while (new_seq > EXT4_MMP_SEQ_MAX); 189 190 return new_seq; 191 #else 192 return EXT2_ET_OP_NOT_SUPPORTED; 193 #endif 194 } 195 196 #ifdef CONFIG_MMP 197 static errcode_t ext2fs_mmp_reset(ext2_filsys fs) 198 { 199 struct mmp_struct *mmp_s = NULL; 200 errcode_t retval = 0; 201 202 if (fs->mmp_buf == NULL) { 203 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf); 204 if (retval) 205 goto out; 206 } 207 208 memset(fs->mmp_buf, 0, fs->blocksize); 209 mmp_s = fs->mmp_buf; 210 211 mmp_s->mmp_magic = EXT4_MMP_MAGIC; 212 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN; 213 mmp_s->mmp_time = 0; 214 #ifdef HAVE_GETHOSTNAME 215 gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename)); 216 #else 217 mmp_s->mmp_nodename[0] = '\0'; 218 #endif 219 strncpy((char *) mmp_s->mmp_bdevname, fs->device_name, 220 sizeof(mmp_s->mmp_bdevname)); 221 222 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval; 223 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL) 224 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL; 225 226 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); 227 out: 228 return retval; 229 } 230 #endif 231 232 errcode_t ext2fs_mmp_update(ext2_filsys fs) 233 { 234 return ext2fs_mmp_update2(fs, 0); 235 } 236 237 errcode_t ext2fs_mmp_clear(ext2_filsys fs) 238 { 239 #ifdef CONFIG_MMP 240 errcode_t retval = 0; 241 242 if (!(fs->flags & EXT2_FLAG_RW)) 243 return EXT2_ET_RO_FILSYS; 244 245 retval = ext2fs_mmp_reset(fs); 246 247 return retval; 248 #else 249 return EXT2_ET_OP_NOT_SUPPORTED; 250 #endif 251 } 252 253 errcode_t ext2fs_mmp_init(ext2_filsys fs) 254 { 255 #ifdef CONFIG_MMP 256 struct ext2_super_block *sb = fs->super; 257 blk64_t mmp_block; 258 errcode_t retval; 259 260 if (sb->s_mmp_update_interval == 0) 261 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL; 262 /* This is probably excessively large, but who knows? */ 263 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL) 264 return EXT2_ET_INVALID_ARGUMENT; 265 266 if (fs->mmp_buf == NULL) { 267 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf); 268 if (retval) 269 goto out; 270 } 271 272 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block); 273 if (retval) 274 goto out; 275 276 sb->s_mmp_block = mmp_block; 277 278 retval = ext2fs_mmp_reset(fs); 279 if (retval) 280 goto out; 281 282 out: 283 return retval; 284 #else 285 return EXT2_ET_OP_NOT_SUPPORTED; 286 #endif 287 } 288 289 #ifndef min 290 #define min(x, y) ((x) < (y) ? (x) : (y)) 291 #endif 292 293 /* 294 * Make sure that the fs is not mounted or being fsck'ed while opening the fs. 295 */ 296 errcode_t ext2fs_mmp_start(ext2_filsys fs) 297 { 298 #ifdef CONFIG_MMP 299 struct mmp_struct *mmp_s; 300 unsigned seq; 301 unsigned int mmp_check_interval; 302 errcode_t retval = 0; 303 304 if (fs->mmp_buf == NULL) { 305 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf); 306 if (retval) 307 goto mmp_error; 308 } 309 310 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); 311 if (retval) 312 goto mmp_error; 313 314 mmp_s = fs->mmp_buf; 315 316 mmp_check_interval = fs->super->s_mmp_update_interval; 317 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL) 318 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL; 319 320 seq = mmp_s->mmp_seq; 321 if (seq == EXT4_MMP_SEQ_CLEAN) 322 goto clean_seq; 323 if (seq == EXT4_MMP_SEQ_FSCK) { 324 retval = EXT2_ET_MMP_FSCK_ON; 325 goto mmp_error; 326 } 327 328 if (seq > EXT4_MMP_SEQ_FSCK) { 329 retval = EXT2_ET_MMP_UNKNOWN_SEQ; 330 goto mmp_error; 331 } 332 333 /* 334 * If check_interval in MMP block is larger, use that instead of 335 * check_interval from the superblock. 336 */ 337 if (mmp_s->mmp_check_interval > mmp_check_interval) 338 mmp_check_interval = mmp_s->mmp_check_interval; 339 340 sleep(min(mmp_check_interval * 2 + 1, mmp_check_interval + 60)); 341 342 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); 343 if (retval) 344 goto mmp_error; 345 346 if (seq != mmp_s->mmp_seq) { 347 retval = EXT2_ET_MMP_FAILED; 348 goto mmp_error; 349 } 350 351 clean_seq: 352 if (!(fs->flags & EXT2_FLAG_RW)) 353 goto mmp_error; 354 355 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq(); 356 #ifdef HAVE_GETHOSTNAME 357 gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename)); 358 #else 359 strcpy(mmp_s->mmp_nodename, "unknown host"); 360 #endif 361 strncpy((char *) mmp_s->mmp_bdevname, fs->device_name, 362 sizeof(mmp_s->mmp_bdevname)); 363 364 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); 365 if (retval) 366 goto mmp_error; 367 368 sleep(min(2 * mmp_check_interval + 1, mmp_check_interval + 60)); 369 370 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); 371 if (retval) 372 goto mmp_error; 373 374 if (seq != mmp_s->mmp_seq) { 375 retval = EXT2_ET_MMP_FAILED; 376 goto mmp_error; 377 } 378 379 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK; 380 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); 381 if (retval) 382 goto mmp_error; 383 384 return 0; 385 386 mmp_error: 387 return retval; 388 #else 389 return EXT2_ET_OP_NOT_SUPPORTED; 390 #endif 391 } 392 393 /* 394 * Clear the MMP usage in the filesystem. If this function returns an 395 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified 396 * by some other process while in use, and changes should be dropped, or 397 * risk filesystem corruption. 398 */ 399 errcode_t ext2fs_mmp_stop(ext2_filsys fs) 400 { 401 #ifdef CONFIG_MMP 402 struct mmp_struct *mmp, *mmp_cmp; 403 errcode_t retval = 0; 404 405 if (!ext2fs_has_feature_mmp(fs->super) || 406 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP) || 407 (fs->mmp_buf == NULL) || (fs->mmp_cmp == NULL)) 408 goto mmp_error; 409 410 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf); 411 if (retval) 412 goto mmp_error; 413 414 /* Check if the MMP block is not changed. */ 415 mmp = fs->mmp_buf; 416 mmp_cmp = fs->mmp_cmp; 417 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) { 418 retval = EXT2_ET_MMP_CHANGE_ABORT; 419 goto mmp_error; 420 } 421 422 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN; 423 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp); 424 425 mmp_error: 426 if (fs->mmp_fd > 0) { 427 close(fs->mmp_fd); 428 fs->mmp_fd = -1; 429 } 430 431 return retval; 432 #else 433 if (!ext2fs_has_feature_mmp(fs->super) || 434 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP)) 435 return 0; 436 437 return EXT2_ET_OP_NOT_SUPPORTED; 438 #endif 439 } 440 441 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60 442 443 /* 444 * Update the on-disk mmp buffer, after checking that it hasn't been changed. 445 */ 446 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately) 447 { 448 #ifdef CONFIG_MMP 449 struct mmp_struct *mmp, *mmp_cmp; 450 struct timeval tv; 451 errcode_t retval = 0; 452 453 if (!ext2fs_has_feature_mmp(fs->super) || 454 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP)) 455 return 0; 456 457 gettimeofday(&tv, 0); 458 if (!immediately && 459 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL) 460 return 0; 461 462 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL); 463 if (retval) 464 goto mmp_error; 465 466 mmp = fs->mmp_buf; 467 mmp_cmp = fs->mmp_cmp; 468 469 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) 470 return EXT2_ET_MMP_CHANGE_ABORT; 471 472 mmp->mmp_time = tv.tv_sec; 473 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK; 474 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf); 475 476 mmp_error: 477 return retval; 478 #else 479 if (!ext2fs_has_feature_mmp(fs->super) || 480 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP)) 481 return 0; 482 483 return EXT2_ET_OP_NOT_SUPPORTED; 484 #endif 485 } 486 #if __GNUC_PREREQ (4, 6) 487 #pragma GCC diagnostic pop 488 #endif