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)
37 struct mmp_struct *mmp_cmp;
40 if ((mmp_blk <= fs->super->s_first_data_block) ||
41 (mmp_blk >= fs->super->s_blocks_count))
42 return EXT2_ET_MMP_BAD_BLOCK;
44 /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
45 * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its
46 * own fd to read the MMP block to ensure that it is using O_DIRECT,
47 * regardless of how the io_manager is doing reads, to avoid caching of
48 * the MMP block by the io_manager or the VM. It needs to be fresh. */
49 if (fs->mmp_fd <= 0) {
50 fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
52 retval = EXT2_ET_MMP_OPEN_DIRECT;
57 if (fs->mmp_cmp == NULL) {
58 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
60 retval = ext2fs_get_memalign(fs->blocksize, align,
66 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
68 mmp_blk * fs->blocksize) {
69 retval = EXT2_ET_LLSEEK_FAILED;
73 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
74 retval = EXT2_ET_SHORT_READ;
78 mmp_cmp = fs->mmp_cmp;
80 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
81 !ext2fs_mmp_csum_verify(fs, mmp_cmp))
82 retval = EXT2_ET_MMP_CSUM_INVALID;
84 #ifdef WORDS_BIGENDIAN
85 ext2fs_swap_mmp(mmp_cmp);
88 if (buf != NULL && buf != fs->mmp_cmp)
89 memcpy(buf, fs->mmp_cmp, fs->blocksize);
91 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
92 retval = EXT2_ET_MMP_MAGIC_INVALID;
99 return EXT2_ET_OP_NOT_SUPPORTED;
103 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
106 struct mmp_struct *mmp_s = buf;
108 errcode_t retval = 0;
110 gettimeofday(&tv, 0);
111 mmp_s->mmp_time = tv.tv_sec;
112 fs->mmp_last_written = tv.tv_sec;
114 if (fs->super->s_mmp_block < fs->super->s_first_data_block ||
115 fs->super->s_mmp_block > ext2fs_blocks_count(fs->super))
116 return EXT2_ET_MMP_BAD_BLOCK;
118 #ifdef WORDS_BIGENDIAN
119 ext2fs_swap_mmp(mmp_s);
122 retval = ext2fs_mmp_csum_set(fs, mmp_s);
126 /* I was tempted to make this use O_DIRECT and the mmp_fd, but
127 * this caused no end of grief, while leaving it as-is works. */
128 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
130 #ifdef WORDS_BIGENDIAN
131 ext2fs_swap_mmp(mmp_s);
134 /* Make sure the block gets to disk quickly */
135 io_channel_flush(fs->io);
138 return EXT2_ET_OP_NOT_SUPPORTED;
143 #define srand(x) srandom(x)
144 #define rand() random()
147 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);
167 return EXT2_ET_OP_NOT_SUPPORTED;
171 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
173 struct mmp_struct *mmp_s = NULL;
174 errcode_t retval = 0;
176 if (fs->mmp_buf == NULL) {
177 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
182 memset(fs->mmp_buf, 0, fs->blocksize);
185 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
186 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
188 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
189 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
191 mmp_s->mmp_nodename[0] = '\0';
193 strncpy(mmp_s->mmp_bdevname, fs->device_name,
194 sizeof(mmp_s->mmp_bdevname));
196 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
197 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
198 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
200 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
205 errcode_t ext2fs_mmp_update(ext2_filsys fs)
207 return ext2fs_mmp_update2(fs, 0);
210 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
213 errcode_t retval = 0;
215 if (!(fs->flags & EXT2_FLAG_RW))
216 return EXT2_ET_RO_FILSYS;
218 retval = ext2fs_mmp_reset(fs);
222 return EXT2_ET_OP_NOT_SUPPORTED;
226 errcode_t ext2fs_mmp_init(ext2_filsys fs)
229 struct ext2_super_block *sb = fs->super;
233 if (sb->s_mmp_update_interval == 0)
234 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
235 /* This is probably excessively large, but who knows? */
236 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
237 return EXT2_ET_INVALID_ARGUMENT;
239 if (fs->mmp_buf == NULL) {
240 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
245 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
249 sb->s_mmp_block = mmp_block;
251 retval = ext2fs_mmp_reset(fs);
258 return EXT2_ET_OP_NOT_SUPPORTED;
263 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
265 errcode_t ext2fs_mmp_start(ext2_filsys fs)
268 struct mmp_struct *mmp_s;
270 unsigned int mmp_check_interval;
271 errcode_t retval = 0;
273 if (fs->mmp_buf == NULL) {
274 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
279 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
285 mmp_check_interval = fs->super->s_mmp_update_interval;
286 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
287 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
289 seq = mmp_s->mmp_seq;
290 if (seq == EXT4_MMP_SEQ_CLEAN)
292 if (seq == EXT4_MMP_SEQ_FSCK) {
293 retval = EXT2_ET_MMP_FSCK_ON;
297 if (seq > EXT4_MMP_SEQ_FSCK) {
298 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
303 * If check_interval in MMP block is larger, use that instead of
304 * check_interval from the superblock.
306 if (mmp_s->mmp_check_interval > mmp_check_interval)
307 mmp_check_interval = mmp_s->mmp_check_interval;
309 sleep(2 * mmp_check_interval + 1);
311 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
315 if (seq != mmp_s->mmp_seq) {
316 retval = EXT2_ET_MMP_FAILED;
321 if (!(fs->flags & EXT2_FLAG_RW))
324 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
325 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
326 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
328 strcpy(mmp_s->mmp_nodename, "unknown host");
330 strncpy(mmp_s->mmp_bdevname, fs->device_name,
331 sizeof(mmp_s->mmp_bdevname));
333 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
337 sleep(2 * mmp_check_interval + 1);
339 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
343 if (seq != mmp_s->mmp_seq) {
344 retval = EXT2_ET_MMP_FAILED;
348 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
349 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
358 return EXT2_ET_OP_NOT_SUPPORTED;
363 * Clear the MMP usage in the filesystem. If this function returns an
364 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
365 * by some other process while in use, and changes should be dropped, or
366 * risk filesystem corruption.
368 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
371 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 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
382 /* Check if the MMP block is not changed. */
384 mmp_cmp = fs->mmp_cmp;
385 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
386 retval = EXT2_ET_MMP_CHANGE_ABORT;
390 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
391 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
394 if (fs->mmp_fd > 0) {
401 return EXT2_ET_OP_NOT_SUPPORTED;
405 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
408 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
410 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
413 struct mmp_struct *mmp, *mmp_cmp;
415 errcode_t retval = 0;
417 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
418 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
421 gettimeofday(&tv, 0);
423 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
426 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
431 mmp_cmp = fs->mmp_cmp;
433 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
434 return EXT2_ET_MMP_CHANGE_ABORT;
436 mmp->mmp_time = tv.tv_sec;
437 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
438 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
443 return EXT2_ET_OP_NOT_SUPPORTED;