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
>= ext2fs_blocks_count(fs
->super
)))
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(void)
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
;
172 static errcode_t
ext2fs_mmp_reset(ext2_filsys fs
)
174 struct mmp_struct
*mmp_s
= NULL
;
175 errcode_t retval
= 0;
177 if (fs
->mmp_buf
== NULL
) {
178 retval
= ext2fs_get_mem(fs
->blocksize
, &fs
->mmp_buf
);
183 memset(fs
->mmp_buf
, 0, fs
->blocksize
);
186 mmp_s
->mmp_magic
= EXT4_MMP_MAGIC
;
187 mmp_s
->mmp_seq
= EXT4_MMP_SEQ_CLEAN
;
189 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
190 gethostname(mmp_s
->mmp_nodename
, sizeof(mmp_s
->mmp_nodename
));
192 mmp_s
->mmp_nodename
[0] = '\0';
194 strncpy(mmp_s
->mmp_bdevname
, fs
->device_name
,
195 sizeof(mmp_s
->mmp_bdevname
));
197 mmp_s
->mmp_check_interval
= fs
->super
->s_mmp_update_interval
;
198 if (mmp_s
->mmp_check_interval
< EXT4_MMP_MIN_CHECK_INTERVAL
)
199 mmp_s
->mmp_check_interval
= EXT4_MMP_MIN_CHECK_INTERVAL
;
201 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
207 errcode_t
ext2fs_mmp_update(ext2_filsys fs
)
209 return ext2fs_mmp_update2(fs
, 0);
212 errcode_t
ext2fs_mmp_clear(ext2_filsys fs
)
215 errcode_t retval
= 0;
217 if (!(fs
->flags
& EXT2_FLAG_RW
))
218 return EXT2_ET_RO_FILSYS
;
220 retval
= ext2fs_mmp_reset(fs
);
224 return EXT2_ET_OP_NOT_SUPPORTED
;
228 errcode_t
ext2fs_mmp_init(ext2_filsys fs
)
231 struct ext2_super_block
*sb
= fs
->super
;
235 if (sb
->s_mmp_update_interval
== 0)
236 sb
->s_mmp_update_interval
= EXT4_MMP_UPDATE_INTERVAL
;
237 /* This is probably excessively large, but who knows? */
238 else if (sb
->s_mmp_update_interval
> EXT4_MMP_MAX_UPDATE_INTERVAL
)
239 return EXT2_ET_INVALID_ARGUMENT
;
241 if (fs
->mmp_buf
== NULL
) {
242 retval
= ext2fs_get_mem(fs
->blocksize
, &fs
->mmp_buf
);
247 retval
= ext2fs_alloc_block2(fs
, 0, fs
->mmp_buf
, &mmp_block
);
251 sb
->s_mmp_block
= mmp_block
;
253 retval
= ext2fs_mmp_reset(fs
);
260 return EXT2_ET_OP_NOT_SUPPORTED
;
265 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
267 errcode_t
ext2fs_mmp_start(ext2_filsys fs
)
270 struct mmp_struct
*mmp_s
;
272 unsigned int mmp_check_interval
;
273 errcode_t retval
= 0;
275 if (fs
->mmp_buf
== NULL
) {
276 retval
= ext2fs_get_mem(fs
->blocksize
, &fs
->mmp_buf
);
281 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
287 mmp_check_interval
= fs
->super
->s_mmp_update_interval
;
288 if (mmp_check_interval
< EXT4_MMP_MIN_CHECK_INTERVAL
)
289 mmp_check_interval
= EXT4_MMP_MIN_CHECK_INTERVAL
;
291 seq
= mmp_s
->mmp_seq
;
292 if (seq
== EXT4_MMP_SEQ_CLEAN
)
294 if (seq
== EXT4_MMP_SEQ_FSCK
) {
295 retval
= EXT2_ET_MMP_FSCK_ON
;
299 if (seq
> EXT4_MMP_SEQ_FSCK
) {
300 retval
= EXT2_ET_MMP_UNKNOWN_SEQ
;
305 * If check_interval in MMP block is larger, use that instead of
306 * check_interval from the superblock.
308 if (mmp_s
->mmp_check_interval
> mmp_check_interval
)
309 mmp_check_interval
= mmp_s
->mmp_check_interval
;
311 sleep(2 * mmp_check_interval
+ 1);
313 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
317 if (seq
!= mmp_s
->mmp_seq
) {
318 retval
= EXT2_ET_MMP_FAILED
;
323 if (!(fs
->flags
& EXT2_FLAG_RW
))
326 mmp_s
->mmp_seq
= seq
= ext2fs_mmp_new_seq();
327 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
328 gethostname(mmp_s
->mmp_nodename
, sizeof(mmp_s
->mmp_nodename
));
330 strcpy(mmp_s
->mmp_nodename
, "unknown host");
332 strncpy(mmp_s
->mmp_bdevname
, fs
->device_name
,
333 sizeof(mmp_s
->mmp_bdevname
));
335 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
339 sleep(2 * mmp_check_interval
+ 1);
341 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
345 if (seq
!= mmp_s
->mmp_seq
) {
346 retval
= EXT2_ET_MMP_FAILED
;
350 mmp_s
->mmp_seq
= EXT4_MMP_SEQ_FSCK
;
351 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
360 return EXT2_ET_OP_NOT_SUPPORTED
;
365 * Clear the MMP usage in the filesystem. If this function returns an
366 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
367 * by some other process while in use, and changes should be dropped, or
368 * risk filesystem corruption.
370 errcode_t
ext2fs_mmp_stop(ext2_filsys fs
)
373 struct mmp_struct
*mmp
, *mmp_cmp
;
374 errcode_t retval
= 0;
376 if (!(fs
->super
->s_feature_incompat
& EXT4_FEATURE_INCOMPAT_MMP
) ||
377 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
380 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
384 /* Check if the MMP block is not changed. */
386 mmp_cmp
= fs
->mmp_cmp
;
387 if (memcmp(mmp
, mmp_cmp
, sizeof(*mmp_cmp
))) {
388 retval
= EXT2_ET_MMP_CHANGE_ABORT
;
392 mmp_cmp
->mmp_seq
= EXT4_MMP_SEQ_CLEAN
;
393 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_cmp
);
396 if (fs
->mmp_fd
> 0) {
403 if (!(fs
->super
->s_feature_incompat
& EXT4_FEATURE_INCOMPAT_MMP
) ||
404 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
407 return EXT2_ET_OP_NOT_SUPPORTED
;
411 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
414 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
416 errcode_t
ext2fs_mmp_update2(ext2_filsys fs
, int immediately
)
419 struct mmp_struct
*mmp
, *mmp_cmp
;
421 errcode_t retval
= 0;
423 if (!(fs
->super
->s_feature_incompat
& EXT4_FEATURE_INCOMPAT_MMP
) ||
424 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
427 gettimeofday(&tv
, 0);
429 tv
.tv_sec
- fs
->mmp_last_written
< EXT2_MIN_MMP_UPDATE_INTERVAL
)
432 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, NULL
);
437 mmp_cmp
= fs
->mmp_cmp
;
439 if (memcmp(mmp
, mmp_cmp
, sizeof(*mmp_cmp
)))
440 return EXT2_ET_MMP_CHANGE_ABORT
;
442 mmp
->mmp_time
= tv
.tv_sec
;
443 mmp
->mmp_seq
= EXT4_MMP_SEQ_FSCK
;
444 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
449 if (!(fs
->super
->s_feature_incompat
& EXT4_FEATURE_INCOMPAT_MMP
) ||
450 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
453 return EXT2_ET_OP_NOT_SUPPORTED
;