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.
15 #ifndef _DEFAULT_SOURCE
16 #define _DEFAULT_SOURCE /* since glibc 2.20 _SVID_SOURCE is deprecated */
26 #include <sys/types.h>
30 #include "ext2fs/ext2_fs.h"
31 #include "ext2fs/ext2fs.h"
37 #if __GNUC_PREREQ (4, 6)
38 #pragma GCC diagnostic push
40 #pragma GCC diagnostic ignored "-Wunused-parameter"
44 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
47 struct mmp_struct *mmp_cmp;
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;
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 int flags = O_RDWR | O_DIRECT;
63 fs->mmp_fd = open(fs->device_name, flags);
67 /* Avoid O_DIRECT for filesystem image files if open
68 * fails, since it breaks when running on tmpfs. */
69 if (errno == EINVAL && (flags & O_DIRECT) &&
70 stat(fs->device_name, &st) == 0 &&
71 S_ISREG(st.st_mode)) {
75 retval = EXT2_ET_MMP_OPEN_DIRECT;
80 if (fs->mmp_cmp == NULL) {
81 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
83 retval = ext2fs_get_memalign(fs->blocksize, align,
89 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
91 mmp_blk * fs->blocksize) {
92 retval = EXT2_ET_LLSEEK_FAILED;
96 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
97 retval = EXT2_ET_SHORT_READ;
101 mmp_cmp = fs->mmp_cmp;
103 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
104 !ext2fs_mmp_csum_verify(fs, mmp_cmp))
105 retval = EXT2_ET_MMP_CSUM_INVALID;
107 #ifdef WORDS_BIGENDIAN
108 ext2fs_swap_mmp(mmp_cmp);
111 if (buf != NULL && buf != fs->mmp_cmp)
112 memcpy(buf, fs->mmp_cmp, fs->blocksize);
114 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
115 retval = EXT2_ET_MMP_MAGIC_INVALID;
122 return EXT2_ET_OP_NOT_SUPPORTED;
126 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
129 struct mmp_struct *mmp_s = buf;
131 errcode_t retval = 0;
133 gettimeofday(&tv, 0);
134 mmp_s->mmp_time = tv.tv_sec;
135 fs->mmp_last_written = tv.tv_sec;
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;
141 #ifdef WORDS_BIGENDIAN
142 ext2fs_swap_mmp(mmp_s);
145 retval = ext2fs_mmp_csum_set(fs, mmp_s);
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);
153 #ifdef WORDS_BIGENDIAN
154 ext2fs_swap_mmp(mmp_s);
157 /* Make sure the block gets to disk quickly */
158 io_channel_flush(fs->io);
161 return EXT2_ET_OP_NOT_SUPPORTED;
166 #define srand(x) srandom(x)
167 #define rand() random()
170 unsigned ext2fs_mmp_new_seq(void)
176 gettimeofday(&tv, 0);
177 srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
179 gettimeofday(&tv, 0);
180 /* Crank the random number generator a few times */
181 for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
186 } while (new_seq > EXT4_MMP_SEQ_MAX);
190 return EXT2_ET_OP_NOT_SUPPORTED;
195 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
197 struct mmp_struct *mmp_s = NULL;
198 errcode_t retval = 0;
200 if (fs->mmp_buf == NULL) {
201 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
206 memset(fs->mmp_buf, 0, fs->blocksize);
209 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
210 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
212 #ifdef HAVE_GETHOSTNAME
213 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
215 mmp_s->mmp_nodename[0] = '\0';
217 strncpy(mmp_s->mmp_bdevname, fs->device_name,
218 sizeof(mmp_s->mmp_bdevname));
220 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
221 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
222 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
224 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
230 errcode_t ext2fs_mmp_update(ext2_filsys fs)
232 return ext2fs_mmp_update2(fs, 0);
235 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
238 errcode_t retval = 0;
240 if (!(fs->flags & EXT2_FLAG_RW))
241 return EXT2_ET_RO_FILSYS;
243 retval = ext2fs_mmp_reset(fs);
247 return EXT2_ET_OP_NOT_SUPPORTED;
251 errcode_t ext2fs_mmp_init(ext2_filsys fs)
254 struct ext2_super_block *sb = fs->super;
258 if (sb->s_mmp_update_interval == 0)
259 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
260 /* This is probably excessively large, but who knows? */
261 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
262 return EXT2_ET_INVALID_ARGUMENT;
264 if (fs->mmp_buf == NULL) {
265 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
270 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
274 sb->s_mmp_block = mmp_block;
276 retval = ext2fs_mmp_reset(fs);
283 return EXT2_ET_OP_NOT_SUPPORTED;
288 #define min(x, y) ((x) < (y) ? (x) : (y))
292 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
294 errcode_t ext2fs_mmp_start(ext2_filsys fs)
297 struct mmp_struct *mmp_s;
299 unsigned int mmp_check_interval;
300 errcode_t retval = 0;
302 if (fs->mmp_buf == NULL) {
303 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
308 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
314 mmp_check_interval = fs->super->s_mmp_update_interval;
315 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
316 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
318 seq = mmp_s->mmp_seq;
319 if (seq == EXT4_MMP_SEQ_CLEAN)
321 if (seq == EXT4_MMP_SEQ_FSCK) {
322 retval = EXT2_ET_MMP_FSCK_ON;
326 if (seq > EXT4_MMP_SEQ_FSCK) {
327 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
332 * If check_interval in MMP block is larger, use that instead of
333 * check_interval from the superblock.
335 if (mmp_s->mmp_check_interval > mmp_check_interval)
336 mmp_check_interval = mmp_s->mmp_check_interval;
338 sleep(min(mmp_check_interval * 2 + 1, mmp_check_interval + 60));
340 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
344 if (seq != mmp_s->mmp_seq) {
345 retval = EXT2_ET_MMP_FAILED;
350 if (!(fs->flags & EXT2_FLAG_RW))
353 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
354 #ifdef HAVE_GETHOSTNAME
355 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
357 strcpy(mmp_s->mmp_nodename, "unknown host");
359 strncpy(mmp_s->mmp_bdevname, fs->device_name,
360 sizeof(mmp_s->mmp_bdevname));
362 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
366 sleep(min(2 * mmp_check_interval + 1, mmp_check_interval + 60));
368 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
372 if (seq != mmp_s->mmp_seq) {
373 retval = EXT2_ET_MMP_FAILED;
377 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
378 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
387 return EXT2_ET_OP_NOT_SUPPORTED;
392 * Clear the MMP usage in the filesystem. If this function returns an
393 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
394 * by some other process while in use, and changes should be dropped, or
395 * risk filesystem corruption.
397 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
400 struct mmp_struct *mmp, *mmp_cmp;
401 errcode_t retval = 0;
403 if (!ext2fs_has_feature_mmp(fs->super) ||
404 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
407 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
411 /* Check if the MMP block is not changed. */
413 mmp_cmp = fs->mmp_cmp;
414 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
415 retval = EXT2_ET_MMP_CHANGE_ABORT;
419 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
420 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
423 if (fs->mmp_fd > 0) {
430 if (!ext2fs_has_feature_mmp(fs->super) ||
431 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
434 return EXT2_ET_OP_NOT_SUPPORTED;
438 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
441 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
443 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
446 struct mmp_struct *mmp, *mmp_cmp;
448 errcode_t retval = 0;
450 if (!ext2fs_has_feature_mmp(fs->super) ||
451 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
454 gettimeofday(&tv, 0);
456 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
459 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
464 mmp_cmp = fs->mmp_cmp;
466 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
467 return EXT2_ET_MMP_CHANGE_ABORT;
469 mmp->mmp_time = tv.tv_sec;
470 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
471 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
476 if (!ext2fs_has_feature_mmp(fs->super) ||
477 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
480 return EXT2_ET_OP_NOT_SUPPORTED;
483 #if __GNUC_PREREQ (4, 6)
484 #pragma GCC diagnostic pop