2 * Helper functions for multiple mount protection (MMP).
4 * Copyright (C) 2011 Whamcloud, Inc.
7 * This file may be redistributed under the terms of the GNU Library
8 * General Public License, version 2.
23 #include <sys/types.h>
27 #include "ext2fs/ext2_fs.h"
28 #include "ext2fs/ext2fs.h"
34 #pragma GCC diagnostic push
36 #pragma GCC diagnostic ignored "-Wunused-parameter"
39 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
42 struct mmp_struct *mmp_cmp;
45 if ((mmp_blk <= fs->super->s_first_data_block) ||
46 (mmp_blk >= ext2fs_blocks_count(fs->super)))
47 return EXT2_ET_MMP_BAD_BLOCK;
49 /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
50 * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its
51 * own fd to read the MMP block to ensure that it is using O_DIRECT,
52 * regardless of how the io_manager is doing reads, to avoid caching of
53 * the MMP block by the io_manager or the VM. It needs to be fresh. */
54 if (fs->mmp_fd <= 0) {
55 fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
57 retval = EXT2_ET_MMP_OPEN_DIRECT;
62 if (fs->mmp_cmp == NULL) {
63 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
65 retval = ext2fs_get_memalign(fs->blocksize, align,
71 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
73 mmp_blk * fs->blocksize) {
74 retval = EXT2_ET_LLSEEK_FAILED;
78 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
79 retval = EXT2_ET_SHORT_READ;
83 mmp_cmp = fs->mmp_cmp;
85 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
86 !ext2fs_mmp_csum_verify(fs, mmp_cmp))
87 retval = EXT2_ET_MMP_CSUM_INVALID;
89 #ifdef WORDS_BIGENDIAN
90 ext2fs_swap_mmp(mmp_cmp);
93 if (buf != NULL && buf != fs->mmp_cmp)
94 memcpy(buf, fs->mmp_cmp, fs->blocksize);
96 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
97 retval = EXT2_ET_MMP_MAGIC_INVALID;
104 return EXT2_ET_OP_NOT_SUPPORTED;
108 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
111 struct mmp_struct *mmp_s = buf;
113 errcode_t retval = 0;
115 gettimeofday(&tv, 0);
116 mmp_s->mmp_time = tv.tv_sec;
117 fs->mmp_last_written = tv.tv_sec;
119 if (fs->super->s_mmp_block < fs->super->s_first_data_block ||
120 fs->super->s_mmp_block > ext2fs_blocks_count(fs->super))
121 return EXT2_ET_MMP_BAD_BLOCK;
123 #ifdef WORDS_BIGENDIAN
124 ext2fs_swap_mmp(mmp_s);
127 retval = ext2fs_mmp_csum_set(fs, mmp_s);
131 /* I was tempted to make this use O_DIRECT and the mmp_fd, but
132 * this caused no end of grief, while leaving it as-is works. */
133 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
135 #ifdef WORDS_BIGENDIAN
136 ext2fs_swap_mmp(mmp_s);
139 /* Make sure the block gets to disk quickly */
140 io_channel_flush(fs->io);
143 return EXT2_ET_OP_NOT_SUPPORTED;
148 #define srand(x) srandom(x)
149 #define rand() random()
152 unsigned ext2fs_mmp_new_seq(void)
158 gettimeofday(&tv, 0);
159 srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
161 gettimeofday(&tv, 0);
162 /* Crank the random number generator a few times */
163 for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
168 } while (new_seq > EXT4_MMP_SEQ_MAX);
172 return EXT2_ET_OP_NOT_SUPPORTED;
177 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
179 struct mmp_struct *mmp_s = NULL;
180 errcode_t retval = 0;
182 if (fs->mmp_buf == NULL) {
183 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
188 memset(fs->mmp_buf, 0, fs->blocksize);
191 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
192 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
194 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
195 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
197 mmp_s->mmp_nodename[0] = '\0';
199 strncpy(mmp_s->mmp_bdevname, fs->device_name,
200 sizeof(mmp_s->mmp_bdevname));
202 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
203 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
204 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
206 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
212 errcode_t ext2fs_mmp_update(ext2_filsys fs)
214 return ext2fs_mmp_update2(fs, 0);
217 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
220 errcode_t retval = 0;
222 if (!(fs->flags & EXT2_FLAG_RW))
223 return EXT2_ET_RO_FILSYS;
225 retval = ext2fs_mmp_reset(fs);
229 return EXT2_ET_OP_NOT_SUPPORTED;
233 errcode_t ext2fs_mmp_init(ext2_filsys fs)
236 struct ext2_super_block *sb = fs->super;
240 if (sb->s_mmp_update_interval == 0)
241 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
242 /* This is probably excessively large, but who knows? */
243 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
244 return EXT2_ET_INVALID_ARGUMENT;
246 if (fs->mmp_buf == NULL) {
247 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
252 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
256 sb->s_mmp_block = mmp_block;
258 retval = ext2fs_mmp_reset(fs);
265 return EXT2_ET_OP_NOT_SUPPORTED;
270 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
272 errcode_t ext2fs_mmp_start(ext2_filsys fs)
275 struct mmp_struct *mmp_s;
277 unsigned int mmp_check_interval;
278 errcode_t retval = 0;
280 if (fs->mmp_buf == NULL) {
281 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
286 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
292 mmp_check_interval = fs->super->s_mmp_update_interval;
293 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
294 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
296 seq = mmp_s->mmp_seq;
297 if (seq == EXT4_MMP_SEQ_CLEAN)
299 if (seq == EXT4_MMP_SEQ_FSCK) {
300 retval = EXT2_ET_MMP_FSCK_ON;
304 if (seq > EXT4_MMP_SEQ_FSCK) {
305 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
310 * If check_interval in MMP block is larger, use that instead of
311 * check_interval from the superblock.
313 if (mmp_s->mmp_check_interval > mmp_check_interval)
314 mmp_check_interval = mmp_s->mmp_check_interval;
316 sleep(2 * mmp_check_interval + 1);
318 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
322 if (seq != mmp_s->mmp_seq) {
323 retval = EXT2_ET_MMP_FAILED;
328 if (!(fs->flags & EXT2_FLAG_RW))
331 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
332 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
333 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
335 strcpy(mmp_s->mmp_nodename, "unknown host");
337 strncpy(mmp_s->mmp_bdevname, fs->device_name,
338 sizeof(mmp_s->mmp_bdevname));
340 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
344 sleep(2 * mmp_check_interval + 1);
346 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
350 if (seq != mmp_s->mmp_seq) {
351 retval = EXT2_ET_MMP_FAILED;
355 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
356 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
365 return EXT2_ET_OP_NOT_SUPPORTED;
370 * Clear the MMP usage in the filesystem. If this function returns an
371 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
372 * by some other process while in use, and changes should be dropped, or
373 * risk filesystem corruption.
375 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
378 struct mmp_struct *mmp, *mmp_cmp;
379 errcode_t retval = 0;
381 if (!ext2fs_has_feature_mmp(fs->super) ||
382 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
385 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
389 /* Check if the MMP block is not changed. */
391 mmp_cmp = fs->mmp_cmp;
392 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
393 retval = EXT2_ET_MMP_CHANGE_ABORT;
397 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
398 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
401 if (fs->mmp_fd > 0) {
408 if (!ext2fs_has_feature_mmp(fs->super) ||
409 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
412 return EXT2_ET_OP_NOT_SUPPORTED;
416 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
419 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
421 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
424 struct mmp_struct *mmp, *mmp_cmp;
426 errcode_t retval = 0;
428 if (!ext2fs_has_feature_mmp(fs->super) ||
429 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
432 gettimeofday(&tv, 0);
434 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
437 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
442 mmp_cmp = fs->mmp_cmp;
444 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
445 return EXT2_ET_MMP_CHANGE_ABORT;
447 mmp->mmp_time = tv.tv_sec;
448 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
449 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
454 if (!ext2fs_has_feature_mmp(fs->super) ||
455 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
458 return EXT2_ET_OP_NOT_SUPPORTED;
461 #pragma GCC diagnostic pop