]> git.ipfire.org Git - thirdparty/e2fsprogs.git/blob - lib/ext2fs/mmp.c
misc: add e2mmpstatus utility via dumpe2fs
[thirdparty/e2fsprogs.git] / lib / ext2fs / mmp.c
1 /*
2 * Helper functions for multiple mount protection (MMP).
3 *
4 * Copyright (C) 2011 Whamcloud, Inc.
5 *
6 * %Begin-Header%
7 * This file may be redistributed under the terms of the GNU Library
8 * General Public License, version 2.
9 * %End-Header%
10 */
11
12 #ifndef _GNU_SOURCE
13 #define _GNU_SOURCE
14 #endif
15 #ifndef _DEFAULT_SOURCE
16 #define _DEFAULT_SOURCE /* since glibc 2.20 _SVID_SOURCE is deprecated */
17 #endif
18
19 #include "config.h"
20
21 #if HAVE_UNISTD_H
22 #include <unistd.h>
23 #endif
24 #include <sys/time.h>
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29
30 #include "ext2fs/ext2_fs.h"
31 #include "ext2fs/ext2fs.h"
32
33 #ifndef O_DIRECT
34 #define O_DIRECT 0
35 #endif
36
37 #pragma GCC diagnostic push
38 #ifndef CONFIG_MMP
39 #pragma GCC diagnostic ignored "-Wunused-parameter"
40 #endif
41
42 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
43 {
44 #ifdef CONFIG_MMP
45 struct mmp_struct *mmp_cmp;
46 errcode_t retval = 0;
47
48 if ((mmp_blk <= fs->super->s_first_data_block) ||
49 (mmp_blk >= ext2fs_blocks_count(fs->super)))
50 return EXT2_ET_MMP_BAD_BLOCK;
51
52 /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
53 * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its
54 * own fd to read the MMP block to ensure that it is using O_DIRECT,
55 * regardless of how the io_manager is doing reads, to avoid caching of
56 * the MMP block by the io_manager or the VM. It needs to be fresh. */
57 if (fs->mmp_fd <= 0) {
58 fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
59 if (fs->mmp_fd < 0) {
60 retval = EXT2_ET_MMP_OPEN_DIRECT;
61 goto out;
62 }
63 }
64
65 if (fs->mmp_cmp == NULL) {
66 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
67
68 retval = ext2fs_get_memalign(fs->blocksize, align,
69 &fs->mmp_cmp);
70 if (retval)
71 return retval;
72 }
73
74 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
75 SEEK_SET) !=
76 mmp_blk * fs->blocksize) {
77 retval = EXT2_ET_LLSEEK_FAILED;
78 goto out;
79 }
80
81 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
82 retval = EXT2_ET_SHORT_READ;
83 goto out;
84 }
85
86 mmp_cmp = fs->mmp_cmp;
87
88 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
89 !ext2fs_mmp_csum_verify(fs, mmp_cmp))
90 retval = EXT2_ET_MMP_CSUM_INVALID;
91
92 #ifdef WORDS_BIGENDIAN
93 ext2fs_swap_mmp(mmp_cmp);
94 #endif
95
96 if (buf != NULL && buf != fs->mmp_cmp)
97 memcpy(buf, fs->mmp_cmp, fs->blocksize);
98
99 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
100 retval = EXT2_ET_MMP_MAGIC_INVALID;
101 goto out;
102 }
103
104 out:
105 return retval;
106 #else
107 return EXT2_ET_OP_NOT_SUPPORTED;
108 #endif
109 }
110
111 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
112 {
113 #ifdef CONFIG_MMP
114 struct mmp_struct *mmp_s = buf;
115 struct timeval tv;
116 errcode_t retval = 0;
117
118 gettimeofday(&tv, 0);
119 mmp_s->mmp_time = tv.tv_sec;
120 fs->mmp_last_written = tv.tv_sec;
121
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;
125
126 #ifdef WORDS_BIGENDIAN
127 ext2fs_swap_mmp(mmp_s);
128 #endif
129
130 retval = ext2fs_mmp_csum_set(fs, mmp_s);
131 if (retval)
132 return retval;
133
134 /* I was tempted to make this use O_DIRECT and the mmp_fd, but
135 * this caused no end of grief, while leaving it as-is works. */
136 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
137
138 #ifdef WORDS_BIGENDIAN
139 ext2fs_swap_mmp(mmp_s);
140 #endif
141
142 /* Make sure the block gets to disk quickly */
143 io_channel_flush(fs->io);
144 return retval;
145 #else
146 return EXT2_ET_OP_NOT_SUPPORTED;
147 #endif
148 }
149
150 #ifdef HAVE_SRANDOM
151 #define srand(x) srandom(x)
152 #define rand() random()
153 #endif
154
155 unsigned ext2fs_mmp_new_seq(void)
156 {
157 #ifdef CONFIG_MMP
158 unsigned new_seq;
159 struct timeval tv;
160
161 gettimeofday(&tv, 0);
162 srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
163
164 gettimeofday(&tv, 0);
165 /* Crank the random number generator a few times */
166 for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
167 rand();
168
169 do {
170 new_seq = rand();
171 } while (new_seq > EXT4_MMP_SEQ_MAX);
172
173 return new_seq;
174 #else
175 return EXT2_ET_OP_NOT_SUPPORTED;
176 #endif
177 }
178
179 #ifdef CONFIG_MMP
180 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
181 {
182 struct mmp_struct *mmp_s = NULL;
183 errcode_t retval = 0;
184
185 if (fs->mmp_buf == NULL) {
186 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
187 if (retval)
188 goto out;
189 }
190
191 memset(fs->mmp_buf, 0, fs->blocksize);
192 mmp_s = fs->mmp_buf;
193
194 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
195 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
196 mmp_s->mmp_time = 0;
197 #ifdef HAVE_GETHOSTNAME
198 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
199 #else
200 mmp_s->mmp_nodename[0] = '\0';
201 #endif
202 strncpy(mmp_s->mmp_bdevname, fs->device_name,
203 sizeof(mmp_s->mmp_bdevname));
204
205 mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
206 if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
207 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
208
209 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
210 out:
211 return retval;
212 }
213 #endif
214
215 errcode_t ext2fs_mmp_update(ext2_filsys fs)
216 {
217 return ext2fs_mmp_update2(fs, 0);
218 }
219
220 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
221 {
222 #ifdef CONFIG_MMP
223 errcode_t retval = 0;
224
225 if (!(fs->flags & EXT2_FLAG_RW))
226 return EXT2_ET_RO_FILSYS;
227
228 retval = ext2fs_mmp_reset(fs);
229
230 return retval;
231 #else
232 return EXT2_ET_OP_NOT_SUPPORTED;
233 #endif
234 }
235
236 errcode_t ext2fs_mmp_init(ext2_filsys fs)
237 {
238 #ifdef CONFIG_MMP
239 struct ext2_super_block *sb = fs->super;
240 blk64_t mmp_block;
241 errcode_t retval;
242
243 if (sb->s_mmp_update_interval == 0)
244 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
245 /* This is probably excessively large, but who knows? */
246 else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
247 return EXT2_ET_INVALID_ARGUMENT;
248
249 if (fs->mmp_buf == NULL) {
250 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
251 if (retval)
252 goto out;
253 }
254
255 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
256 if (retval)
257 goto out;
258
259 sb->s_mmp_block = mmp_block;
260
261 retval = ext2fs_mmp_reset(fs);
262 if (retval)
263 goto out;
264
265 out:
266 return retval;
267 #else
268 return EXT2_ET_OP_NOT_SUPPORTED;
269 #endif
270 }
271
272 #ifndef min
273 #define min(x, y) ((x) < (y) ? (x) : (y))
274 #endif
275
276 /*
277 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
278 */
279 errcode_t ext2fs_mmp_start(ext2_filsys fs)
280 {
281 #ifdef CONFIG_MMP
282 struct mmp_struct *mmp_s;
283 unsigned seq;
284 unsigned int mmp_check_interval;
285 errcode_t retval = 0;
286
287 if (fs->mmp_buf == NULL) {
288 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
289 if (retval)
290 goto mmp_error;
291 }
292
293 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
294 if (retval)
295 goto mmp_error;
296
297 mmp_s = fs->mmp_buf;
298
299 mmp_check_interval = fs->super->s_mmp_update_interval;
300 if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
301 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
302
303 seq = mmp_s->mmp_seq;
304 if (seq == EXT4_MMP_SEQ_CLEAN)
305 goto clean_seq;
306 if (seq == EXT4_MMP_SEQ_FSCK) {
307 retval = EXT2_ET_MMP_FSCK_ON;
308 goto mmp_error;
309 }
310
311 if (seq > EXT4_MMP_SEQ_FSCK) {
312 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
313 goto mmp_error;
314 }
315
316 /*
317 * If check_interval in MMP block is larger, use that instead of
318 * check_interval from the superblock.
319 */
320 if (mmp_s->mmp_check_interval > mmp_check_interval)
321 mmp_check_interval = mmp_s->mmp_check_interval;
322
323 sleep(min(mmp_check_interval * 2 + 1, mmp_check_interval + 60));
324
325 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
326 if (retval)
327 goto mmp_error;
328
329 if (seq != mmp_s->mmp_seq) {
330 retval = EXT2_ET_MMP_FAILED;
331 goto mmp_error;
332 }
333
334 clean_seq:
335 if (!(fs->flags & EXT2_FLAG_RW))
336 goto mmp_error;
337
338 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
339 #ifdef HAVE_GETHOSTNAME
340 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
341 #else
342 strcpy(mmp_s->mmp_nodename, "unknown host");
343 #endif
344 strncpy(mmp_s->mmp_bdevname, fs->device_name,
345 sizeof(mmp_s->mmp_bdevname));
346
347 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
348 if (retval)
349 goto mmp_error;
350
351 sleep(min(2 * mmp_check_interval + 1, mmp_check_interval + 60));
352
353 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
354 if (retval)
355 goto mmp_error;
356
357 if (seq != mmp_s->mmp_seq) {
358 retval = EXT2_ET_MMP_FAILED;
359 goto mmp_error;
360 }
361
362 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
363 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
364 if (retval)
365 goto mmp_error;
366
367 return 0;
368
369 mmp_error:
370 return retval;
371 #else
372 return EXT2_ET_OP_NOT_SUPPORTED;
373 #endif
374 }
375
376 /*
377 * Clear the MMP usage in the filesystem. If this function returns an
378 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
379 * by some other process while in use, and changes should be dropped, or
380 * risk filesystem corruption.
381 */
382 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
383 {
384 #ifdef CONFIG_MMP
385 struct mmp_struct *mmp, *mmp_cmp;
386 errcode_t retval = 0;
387
388 if (!ext2fs_has_feature_mmp(fs->super) ||
389 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
390 goto mmp_error;
391
392 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
393 if (retval)
394 goto mmp_error;
395
396 /* Check if the MMP block is not changed. */
397 mmp = fs->mmp_buf;
398 mmp_cmp = fs->mmp_cmp;
399 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
400 retval = EXT2_ET_MMP_CHANGE_ABORT;
401 goto mmp_error;
402 }
403
404 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
405 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
406
407 mmp_error:
408 if (fs->mmp_fd > 0) {
409 close(fs->mmp_fd);
410 fs->mmp_fd = -1;
411 }
412
413 return retval;
414 #else
415 if (!ext2fs_has_feature_mmp(fs->super) ||
416 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
417 return 0;
418
419 return EXT2_ET_OP_NOT_SUPPORTED;
420 #endif
421 }
422
423 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
424
425 /*
426 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
427 */
428 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
429 {
430 #ifdef CONFIG_MMP
431 struct mmp_struct *mmp, *mmp_cmp;
432 struct timeval tv;
433 errcode_t retval = 0;
434
435 if (!ext2fs_has_feature_mmp(fs->super) ||
436 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
437 return 0;
438
439 gettimeofday(&tv, 0);
440 if (!immediately &&
441 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
442 return 0;
443
444 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
445 if (retval)
446 goto mmp_error;
447
448 mmp = fs->mmp_buf;
449 mmp_cmp = fs->mmp_cmp;
450
451 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
452 return EXT2_ET_MMP_CHANGE_ABORT;
453
454 mmp->mmp_time = tv.tv_sec;
455 mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
456 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
457
458 mmp_error:
459 return retval;
460 #else
461 if (!ext2fs_has_feature_mmp(fs->super) ||
462 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
463 return 0;
464
465 return EXT2_ET_OP_NOT_SUPPORTED;
466 #endif
467 }
468 #pragma GCC diagnostic pop