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 Public
23 #include <sys/types.h>
27 #include "ext2fs/ext2_fs.h"
28 #include "ext2fs/ext2fs.h"
30 static int mmp_pagesize(void)
33 int sysval = sysconf(_SC_PAGESIZE);
36 #endif /* _SC_PAGESIZE */
37 #ifdef HAVE_GETPAGESIZE
48 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
50 struct mmp_struct *mmp_cmp;
53 if ((mmp_blk <= fs->super->s_first_data_block) ||
54 (mmp_blk >= fs->super->s_blocks_count))
55 return EXT2_ET_MMP_BAD_BLOCK;
57 if (fs->mmp_cmp == NULL) {
58 /* O_DIRECT in linux 2.4: page aligned
59 * O_DIRECT in linux 2.6: sector aligned
60 * A filesystem cannot be created with blocksize < sector size,
61 * or with blocksize > page_size. */
62 int bufsize = fs->blocksize;
64 if (bufsize < mmp_pagesize())
65 bufsize = mmp_pagesize();
66 retval = ext2fs_get_memalign(bufsize, bufsize, &fs->mmp_cmp);
71 /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
72 * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its
73 * own fd to read the MMP block to ensure that it is using O_DIRECT,
74 * regardless of how the io_manager is doing reads, to avoid caching of
75 * the MMP block by the io_manager or the VM. It needs to be fresh. */
76 if (fs->mmp_fd <= 0) {
77 fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
79 retval = EXT2_ET_MMP_OPEN_DIRECT;
84 if (ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize, SEEK_SET) !=
85 mmp_blk * fs->blocksize) {
86 retval = EXT2_ET_LLSEEK_FAILED;
90 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
91 retval = EXT2_ET_SHORT_READ;
95 mmp_cmp = fs->mmp_cmp;
96 #ifdef WORDS_BIGENDIAN
97 ext2fs_swap_mmp(mmp_cmp);
100 if (buf != NULL && buf != fs->mmp_cmp)
101 memcpy(buf, fs->mmp_cmp, fs->blocksize);
103 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
104 retval = EXT2_ET_MMP_MAGIC_INVALID;
112 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
114 struct mmp_struct *mmp_s = buf;
116 errcode_t retval = 0;
118 gettimeofday(&tv, 0);
119 mmp_s->mmp_time = tv.tv_sec;
120 fs->mmp_last_written = tv.tv_sec;
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;
126 #ifdef WORDS_BIGENDIAN
127 ext2fs_swap_mmp(mmp_s);
130 /* I was tempted to make this use O_DIRECT and the mmp_fd, but
131 * this caused no end of grief, while leaving it as-is works. */
132 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
134 #ifdef WORDS_BIGENDIAN
135 ext2fs_swap_mmp(mmp_s);
138 /* Make sure the block gets to disk quickly */
139 io_channel_flush(fs->io);
144 #define srand(x) srandom(x)
145 #define rand() random()
148 unsigned ext2fs_mmp_new_seq()
153 gettimeofday(&tv, 0);
154 srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
156 gettimeofday(&tv, 0);
157 /* Crank the random number generator a few times */
158 for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
163 } while (new_seq > EXT4_MMP_SEQ_MAX);
168 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
170 struct mmp_struct *mmp_s = NULL;
171 errcode_t retval = 0;
173 if (fs->mmp_buf == NULL) {
174 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
179 memset(fs->mmp_buf, 0, fs->blocksize);
182 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
183 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
185 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
186 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
188 mmp_s->mmp_nodename[0] = '\0';
190 strncpy(mmp_s->mmp_bdevname, fs->device_name,
191 sizeof(mmp_s->mmp_bdevname));
193 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
194 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
195 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
197 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
202 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
204 errcode_t retval = 0;
206 if (!(fs->flags & EXT2_FLAG_RW))
207 return EXT2_ET_RO_FILSYS;
209 retval = ext2fs_mmp_reset(fs);
214 errcode_t ext2fs_mmp_init(ext2_filsys fs)
216 struct ext2_super_block *sb = fs->super;
220 if (sb->s_mmp_update_interval == 0)
221 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
222 /* This is probably excessively large, but who knows? */
223 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
224 return EXT2_ET_INVALID_ARGUMENT;
226 if (fs->mmp_buf == NULL) {
227 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
232 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
236 sb->s_mmp_block = mmp_block;
238 retval = ext2fs_mmp_reset(fs);
247 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
249 errcode_t ext2fs_mmp_start(ext2_filsys fs)
251 struct mmp_struct *mmp_s;
253 unsigned int mmp_check_interval;
254 errcode_t retval = 0;
256 if (fs->mmp_buf == NULL) {
257 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
262 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
268 mmp_check_interval = fs->super->s_mmp_update_interval;
269 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
270 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
272 seq = mmp_s->mmp_seq;
273 if (seq == EXT4_MMP_SEQ_CLEAN)
275 if (seq == EXT4_MMP_SEQ_FSCK) {
276 retval = EXT2_ET_MMP_FSCK_ON;
280 if (seq > EXT4_MMP_SEQ_FSCK) {
281 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
286 * If check_interval in MMP block is larger, use that instead of
287 * check_interval from the superblock.
289 if (mmp_s->mmp_check_interval > mmp_check_interval)
290 mmp_check_interval = mmp_s->mmp_check_interval;
292 sleep(2 * mmp_check_interval + 1);
294 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
298 if (seq != mmp_s->mmp_seq) {
299 retval = EXT2_ET_MMP_FAILED;
304 if (!(fs->flags & EXT2_FLAG_RW))
307 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
308 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
309 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
311 strcpy(mmp_s->mmp_nodename, "unknown host");
313 strncpy(mmp_s->mmp_bdevname, fs->device_name,
314 sizeof(mmp_s->mmp_bdevname));
316 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
320 sleep(2 * mmp_check_interval + 1);
322 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
326 if (seq != mmp_s->mmp_seq) {
327 retval = EXT2_ET_MMP_FAILED;
331 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
332 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
343 * Clear the MMP usage in the filesystem. If this function returns an
344 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
345 * by some other process while in use, and changes should be dropped, or
346 * risk filesystem corruption.
348 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
350 struct mmp_struct *mmp, *mmp_cmp;
351 errcode_t retval = 0;
353 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
354 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
357 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
361 /* Check if the MMP block is not changed. */
363 mmp_cmp = fs->mmp_cmp;
364 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
365 retval = EXT2_ET_MMP_CHANGE_ABORT;
369 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
370 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
373 if (fs->mmp_fd > 0) {
381 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
384 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
386 errcode_t ext2fs_mmp_update(ext2_filsys fs)
388 struct mmp_struct *mmp, *mmp_cmp;
390 errcode_t retval = 0;
392 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
393 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
396 gettimeofday(&tv, 0);
397 if (tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
400 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
405 mmp_cmp = fs->mmp_cmp;
407 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
408 return EXT2_ET_MMP_CHANGE_ABORT;
410 mmp->mmp_time = tv.tv_sec;
411 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
412 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);