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