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