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) {
61 int flags
= O_RDONLY
| O_DIRECT
;
64 * There is no reason for using O_DIRECT if we're working with
65 * regular file. Disabling it also avoids problems with
66 * alignment when the device of the host file system has sector
67 * size larger than blocksize of the fs we're working with.
69 if (stat(fs
->device_name
, &st
) == 0 &&
73 fs
->mmp_fd
= open(fs
->device_name
, flags
);
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)
175 unsigned long pid
= getpid();
177 gettimeofday(&tv
, 0);
178 pid
= (pid
>> 16) | ((pid
& 0xFFFF) << 16);
179 srand(pid
^ getuid() ^ tv
.tv_sec
^ tv
.tv_usec
);
181 gettimeofday(&tv
, 0);
182 /* Crank the random number generator a few times */
183 for (new_seq
= (tv
.tv_sec
^ tv
.tv_usec
) & 0x1F; new_seq
> 0; new_seq
--)
188 } while (new_seq
> EXT4_MMP_SEQ_MAX
);
192 return EXT2_ET_OP_NOT_SUPPORTED
;
197 static errcode_t
ext2fs_mmp_reset(ext2_filsys fs
)
199 struct mmp_struct
*mmp_s
= NULL
;
200 errcode_t retval
= 0;
202 if (fs
->mmp_buf
== NULL
) {
203 retval
= ext2fs_get_mem(fs
->blocksize
, &fs
->mmp_buf
);
208 memset(fs
->mmp_buf
, 0, fs
->blocksize
);
211 mmp_s
->mmp_magic
= EXT4_MMP_MAGIC
;
212 mmp_s
->mmp_seq
= EXT4_MMP_SEQ_CLEAN
;
214 #ifdef HAVE_GETHOSTNAME
215 gethostname((char *) mmp_s
->mmp_nodename
, sizeof(mmp_s
->mmp_nodename
));
217 mmp_s
->mmp_nodename
[0] = '\0';
219 strncpy((char *) mmp_s
->mmp_bdevname
, fs
->device_name
,
220 sizeof(mmp_s
->mmp_bdevname
));
222 mmp_s
->mmp_check_interval
= fs
->super
->s_mmp_update_interval
;
223 if (mmp_s
->mmp_check_interval
< EXT4_MMP_MIN_CHECK_INTERVAL
)
224 mmp_s
->mmp_check_interval
= EXT4_MMP_MIN_CHECK_INTERVAL
;
226 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
232 errcode_t
ext2fs_mmp_update(ext2_filsys fs
)
234 return ext2fs_mmp_update2(fs
, 0);
237 errcode_t
ext2fs_mmp_clear(ext2_filsys fs
)
240 errcode_t retval
= 0;
242 if (!(fs
->flags
& EXT2_FLAG_RW
))
243 return EXT2_ET_RO_FILSYS
;
245 retval
= ext2fs_mmp_reset(fs
);
249 return EXT2_ET_OP_NOT_SUPPORTED
;
253 errcode_t
ext2fs_mmp_init(ext2_filsys fs
)
256 struct ext2_super_block
*sb
= fs
->super
;
260 if (sb
->s_mmp_update_interval
== 0)
261 sb
->s_mmp_update_interval
= EXT4_MMP_UPDATE_INTERVAL
;
262 /* This is probably excessively large, but who knows? */
263 else if (sb
->s_mmp_update_interval
> EXT4_MMP_MAX_UPDATE_INTERVAL
)
264 return EXT2_ET_INVALID_ARGUMENT
;
266 if (fs
->mmp_buf
== NULL
) {
267 retval
= ext2fs_get_mem(fs
->blocksize
, &fs
->mmp_buf
);
272 retval
= ext2fs_alloc_block2(fs
, 0, fs
->mmp_buf
, &mmp_block
);
276 sb
->s_mmp_block
= mmp_block
;
278 retval
= ext2fs_mmp_reset(fs
);
285 return EXT2_ET_OP_NOT_SUPPORTED
;
290 #define min(x, y) ((x) < (y) ? (x) : (y))
294 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
296 errcode_t
ext2fs_mmp_start(ext2_filsys fs
)
299 struct mmp_struct
*mmp_s
;
301 unsigned int mmp_check_interval
;
302 errcode_t retval
= 0;
304 if (fs
->mmp_buf
== NULL
) {
305 retval
= ext2fs_get_mem(fs
->blocksize
, &fs
->mmp_buf
);
310 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
316 mmp_check_interval
= fs
->super
->s_mmp_update_interval
;
317 if (mmp_check_interval
< EXT4_MMP_MIN_CHECK_INTERVAL
)
318 mmp_check_interval
= EXT4_MMP_MIN_CHECK_INTERVAL
;
320 seq
= mmp_s
->mmp_seq
;
321 if (seq
== EXT4_MMP_SEQ_CLEAN
)
323 if (seq
== EXT4_MMP_SEQ_FSCK
) {
324 retval
= EXT2_ET_MMP_FSCK_ON
;
328 if (seq
> EXT4_MMP_SEQ_FSCK
) {
329 retval
= EXT2_ET_MMP_UNKNOWN_SEQ
;
334 * If check_interval in MMP block is larger, use that instead of
335 * check_interval from the superblock.
337 if (mmp_s
->mmp_check_interval
> mmp_check_interval
)
338 mmp_check_interval
= mmp_s
->mmp_check_interval
;
340 sleep(min(mmp_check_interval
* 2 + 1, mmp_check_interval
+ 60));
342 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
346 if (seq
!= mmp_s
->mmp_seq
) {
347 retval
= EXT2_ET_MMP_FAILED
;
352 if (!(fs
->flags
& EXT2_FLAG_RW
))
355 mmp_s
->mmp_seq
= seq
= ext2fs_mmp_new_seq();
356 #ifdef HAVE_GETHOSTNAME
357 gethostname((char *) mmp_s
->mmp_nodename
, sizeof(mmp_s
->mmp_nodename
));
359 strcpy((char *) mmp_s
->mmp_nodename
, "unknown host");
361 strncpy((char *) mmp_s
->mmp_bdevname
, fs
->device_name
,
362 sizeof(mmp_s
->mmp_bdevname
));
364 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
368 sleep(min(2 * mmp_check_interval
+ 1, mmp_check_interval
+ 60));
370 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
374 if (seq
!= mmp_s
->mmp_seq
) {
375 retval
= EXT2_ET_MMP_FAILED
;
379 mmp_s
->mmp_seq
= EXT4_MMP_SEQ_FSCK
;
380 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
389 return EXT2_ET_OP_NOT_SUPPORTED
;
394 * Clear the MMP usage in the filesystem. If this function returns an
395 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
396 * by some other process while in use, and changes should be dropped, or
397 * risk filesystem corruption.
399 errcode_t
ext2fs_mmp_stop(ext2_filsys fs
)
402 struct mmp_struct
*mmp
, *mmp_cmp
;
403 errcode_t retval
= 0;
405 if (!ext2fs_has_feature_mmp(fs
->super
) ||
406 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
) ||
407 (fs
->mmp_buf
== NULL
) || (fs
->mmp_cmp
== NULL
))
410 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, NULL
);
414 /* Check if the MMP block is not changed. */
416 mmp_cmp
= fs
->mmp_cmp
;
417 if (memcmp(mmp
, mmp_cmp
, sizeof(*mmp_cmp
))) {
418 retval
= EXT2_ET_MMP_CHANGE_ABORT
;
422 mmp_cmp
->mmp_seq
= EXT4_MMP_SEQ_CLEAN
;
423 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_cmp
);
426 if (fs
->mmp_fd
> 0) {
433 if (!ext2fs_has_feature_mmp(fs
->super
) ||
434 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
437 return EXT2_ET_OP_NOT_SUPPORTED
;
441 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
444 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
446 errcode_t
ext2fs_mmp_update2(ext2_filsys fs
, int immediately
)
449 struct mmp_struct
*mmp
, *mmp_cmp
;
451 errcode_t retval
= 0;
453 if (!ext2fs_has_feature_mmp(fs
->super
) ||
454 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
457 gettimeofday(&tv
, 0);
459 tv
.tv_sec
- fs
->mmp_last_written
< EXT2_MIN_MMP_UPDATE_INTERVAL
)
462 retval
= ext2fs_mmp_read(fs
, fs
->super
->s_mmp_block
, NULL
);
467 mmp_cmp
= fs
->mmp_cmp
;
469 if (memcmp(mmp
, mmp_cmp
, sizeof(*mmp_cmp
)))
470 return EXT2_ET_MMP_CHANGE_ABORT
;
472 mmp
->mmp_time
= tv
.tv_sec
;
473 mmp
->mmp_seq
= EXT4_MMP_SEQ_FSCK
;
474 retval
= ext2fs_mmp_write(fs
, fs
->super
->s_mmp_block
, fs
->mmp_buf
);
479 if (!ext2fs_has_feature_mmp(fs
->super
) ||
480 !(fs
->flags
& EXT2_FLAG_RW
) || (fs
->flags
& EXT2_FLAG_SKIP_MMP
))
483 return EXT2_ET_OP_NOT_SUPPORTED
;
486 #if __GNUC_PREREQ (4, 6)
487 #pragma GCC diagnostic pop