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 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
36 struct mmp_struct *mmp_cmp;
39 if ((mmp_blk <= fs->super->s_first_data_block) ||
40 (mmp_blk >= fs->super->s_blocks_count))
41 return EXT2_ET_MMP_BAD_BLOCK;
43 /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
44 * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its
45 * own fd to read the MMP block to ensure that it is using O_DIRECT,
46 * regardless of how the io_manager is doing reads, to avoid caching of
47 * the MMP block by the io_manager or the VM. It needs to be fresh. */
48 if (fs->mmp_fd <= 0) {
49 fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
51 retval = EXT2_ET_MMP_OPEN_DIRECT;
56 if (fs->mmp_cmp == NULL) {
57 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
59 retval = ext2fs_get_memalign(fs->blocksize, align,
65 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
67 mmp_blk * fs->blocksize) {
68 retval = EXT2_ET_LLSEEK_FAILED;
72 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
73 retval = EXT2_ET_SHORT_READ;
77 mmp_cmp = fs->mmp_cmp;
78 #ifdef WORDS_BIGENDIAN
79 ext2fs_swap_mmp(mmp_cmp);
82 if (buf != NULL && buf != fs->mmp_cmp)
83 memcpy(buf, fs->mmp_cmp, fs->blocksize);
85 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
86 retval = EXT2_ET_MMP_MAGIC_INVALID;
94 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
96 struct mmp_struct *mmp_s = buf;
100 gettimeofday(&tv, 0);
101 mmp_s->mmp_time = tv.tv_sec;
102 fs->mmp_last_written = tv.tv_sec;
104 if (fs->super->s_mmp_block < fs->super->s_first_data_block ||
105 fs->super->s_mmp_block > ext2fs_blocks_count(fs->super))
106 return EXT2_ET_MMP_BAD_BLOCK;
108 #ifdef WORDS_BIGENDIAN
109 ext2fs_swap_mmp(mmp_s);
112 /* I was tempted to make this use O_DIRECT and the mmp_fd, but
113 * this caused no end of grief, while leaving it as-is works. */
114 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
116 #ifdef WORDS_BIGENDIAN
117 ext2fs_swap_mmp(mmp_s);
120 /* Make sure the block gets to disk quickly */
121 io_channel_flush(fs->io);
126 #define srand(x) srandom(x)
127 #define rand() random()
130 unsigned ext2fs_mmp_new_seq()
135 gettimeofday(&tv, 0);
136 srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
138 gettimeofday(&tv, 0);
139 /* Crank the random number generator a few times */
140 for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
145 } while (new_seq > EXT4_MMP_SEQ_MAX);
150 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
152 struct mmp_struct *mmp_s = NULL;
153 errcode_t retval = 0;
155 if (fs->mmp_buf == NULL) {
156 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
161 memset(fs->mmp_buf, 0, fs->blocksize);
164 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
165 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
167 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
168 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
170 mmp_s->mmp_nodename[0] = '\0';
172 strncpy(mmp_s->mmp_bdevname, fs->device_name,
173 sizeof(mmp_s->mmp_bdevname));
175 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
176 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
177 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
179 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
184 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
186 errcode_t retval = 0;
188 if (!(fs->flags & EXT2_FLAG_RW))
189 return EXT2_ET_RO_FILSYS;
191 retval = ext2fs_mmp_reset(fs);
196 errcode_t ext2fs_mmp_init(ext2_filsys fs)
198 struct ext2_super_block *sb = fs->super;
202 if (sb->s_mmp_update_interval == 0)
203 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
204 /* This is probably excessively large, but who knows? */
205 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
206 return EXT2_ET_INVALID_ARGUMENT;
208 if (fs->mmp_buf == NULL) {
209 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
214 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
218 sb->s_mmp_block = mmp_block;
220 retval = ext2fs_mmp_reset(fs);
229 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
231 errcode_t ext2fs_mmp_start(ext2_filsys fs)
233 struct mmp_struct *mmp_s;
235 unsigned int mmp_check_interval;
236 errcode_t retval = 0;
238 if (fs->mmp_buf == NULL) {
239 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
244 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
250 mmp_check_interval = fs->super->s_mmp_update_interval;
251 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
252 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
254 seq = mmp_s->mmp_seq;
255 if (seq == EXT4_MMP_SEQ_CLEAN)
257 if (seq == EXT4_MMP_SEQ_FSCK) {
258 retval = EXT2_ET_MMP_FSCK_ON;
262 if (seq > EXT4_MMP_SEQ_FSCK) {
263 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
268 * If check_interval in MMP block is larger, use that instead of
269 * check_interval from the superblock.
271 if (mmp_s->mmp_check_interval > mmp_check_interval)
272 mmp_check_interval = mmp_s->mmp_check_interval;
274 sleep(2 * mmp_check_interval + 1);
276 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
280 if (seq != mmp_s->mmp_seq) {
281 retval = EXT2_ET_MMP_FAILED;
286 if (!(fs->flags & EXT2_FLAG_RW))
289 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
290 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
291 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
293 strcpy(mmp_s->mmp_nodename, "unknown host");
295 strncpy(mmp_s->mmp_bdevname, fs->device_name,
296 sizeof(mmp_s->mmp_bdevname));
298 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
302 sleep(2 * mmp_check_interval + 1);
304 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
308 if (seq != mmp_s->mmp_seq) {
309 retval = EXT2_ET_MMP_FAILED;
313 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
314 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
325 * Clear the MMP usage in the filesystem. If this function returns an
326 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
327 * by some other process while in use, and changes should be dropped, or
328 * risk filesystem corruption.
330 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
332 struct mmp_struct *mmp, *mmp_cmp;
333 errcode_t retval = 0;
335 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
336 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
339 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
343 /* Check if the MMP block is not changed. */
345 mmp_cmp = fs->mmp_cmp;
346 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
347 retval = EXT2_ET_MMP_CHANGE_ABORT;
351 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
352 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
355 if (fs->mmp_fd > 0) {
363 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
366 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
368 errcode_t ext2fs_mmp_update(ext2_filsys fs)
370 struct mmp_struct *mmp, *mmp_cmp;
372 errcode_t retval = 0;
374 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
375 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
378 gettimeofday(&tv, 0);
379 if (tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
382 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
387 mmp_cmp = fs->mmp_cmp;
389 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
390 return EXT2_ET_MMP_CHANGE_ABORT;
392 mmp->mmp_time = tv.tv_sec;
393 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
394 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);