]> git.ipfire.org Git - thirdparty/e2fsprogs.git/blob - lib/ext2fs/mmp.c
8d927d92db765f4cf33fe402171700c410f33e9f
[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
16 #include "config.h"
17
18 #if HAVE_UNISTD_H
19 #include <unistd.h>
20 #endif
21 #include <sys/time.h>
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26
27 #include "ext2fs/ext2_fs.h"
28 #include "ext2fs/ext2fs.h"
29
30 #ifndef O_DIRECT
31 #define O_DIRECT 0
32 #endif
33
34 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
35 {
36 #ifdef CONFIG_MMP
37 struct mmp_struct *mmp_cmp;
38 errcode_t retval = 0;
39
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;
43
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);
51 if (fs->mmp_fd < 0) {
52 retval = EXT2_ET_MMP_OPEN_DIRECT;
53 goto out;
54 }
55 }
56
57 if (fs->mmp_cmp == NULL) {
58 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
59
60 retval = ext2fs_get_memalign(fs->blocksize, align,
61 &fs->mmp_cmp);
62 if (retval)
63 return retval;
64 }
65
66 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
67 SEEK_SET) !=
68 mmp_blk * fs->blocksize) {
69 retval = EXT2_ET_LLSEEK_FAILED;
70 goto out;
71 }
72
73 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
74 retval = EXT2_ET_SHORT_READ;
75 goto out;
76 }
77
78 mmp_cmp = fs->mmp_cmp;
79
80 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
81 !ext2fs_mmp_csum_verify(fs, mmp_cmp))
82 retval = EXT2_ET_MMP_CSUM_INVALID;
83
84 #ifdef WORDS_BIGENDIAN
85 ext2fs_swap_mmp(mmp_cmp);
86 #endif
87
88 if (buf != NULL && buf != fs->mmp_cmp)
89 memcpy(buf, fs->mmp_cmp, fs->blocksize);
90
91 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
92 retval = EXT2_ET_MMP_MAGIC_INVALID;
93 goto out;
94 }
95
96 out:
97 return retval;
98 #else
99 return EXT2_ET_OP_NOT_SUPPORTED;
100 #endif
101 }
102
103 errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
104 {
105 #ifdef CONFIG_MMP
106 struct mmp_struct *mmp_s = buf;
107 struct timeval tv;
108 errcode_t retval = 0;
109
110 gettimeofday(&tv, 0);
111 mmp_s->mmp_time = tv.tv_sec;
112 fs->mmp_last_written = tv.tv_sec;
113
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;
117
118 #ifdef WORDS_BIGENDIAN
119 ext2fs_swap_mmp(mmp_s);
120 #endif
121
122 retval = ext2fs_mmp_csum_set(fs, mmp_s);
123 if (retval)
124 return retval;
125
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);
129
130 #ifdef WORDS_BIGENDIAN
131 ext2fs_swap_mmp(mmp_s);
132 #endif
133
134 /* Make sure the block gets to disk quickly */
135 io_channel_flush(fs->io);
136 return retval;
137 #else
138 return EXT2_ET_OP_NOT_SUPPORTED;
139 #endif
140 }
141
142 #ifdef HAVE_SRANDOM
143 #define srand(x) srandom(x)
144 #define rand() random()
145 #endif
146
147 unsigned ext2fs_mmp_new_seq(void)
148 {
149 #ifdef CONFIG_MMP
150 unsigned new_seq;
151 struct timeval tv;
152
153 gettimeofday(&tv, 0);
154 srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
155
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--)
159 rand();
160
161 do {
162 new_seq = rand();
163 } while (new_seq > EXT4_MMP_SEQ_MAX);
164
165 return new_seq;
166 #else
167 return EXT2_ET_OP_NOT_SUPPORTED;
168 #endif
169 }
170
171 #ifdef CONFIG_MMP
172 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
173 {
174 struct mmp_struct *mmp_s = NULL;
175 errcode_t retval = 0;
176
177 if (fs->mmp_buf == NULL) {
178 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
179 if (retval)
180 goto out;
181 }
182
183 memset(fs->mmp_buf, 0, fs->blocksize);
184 mmp_s = fs->mmp_buf;
185
186 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
187 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
188 mmp_s->mmp_time = 0;
189 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
190 gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
191 #else
192 mmp_s->mmp_nodename[0] = '\0';
193 #endif
194 strncpy(mmp_s->mmp_bdevname, fs->device_name,
195 sizeof(mmp_s->mmp_bdevname));
196
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;
200
201 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
202 out:
203 return retval;
204 }
205 #endif
206
207 errcode_t ext2fs_mmp_update(ext2_filsys fs)
208 {
209 return ext2fs_mmp_update2(fs, 0);
210 }
211
212 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
213 {
214 #ifdef CONFIG_MMP
215 errcode_t retval = 0;
216
217 if (!(fs->flags & EXT2_FLAG_RW))
218 return EXT2_ET_RO_FILSYS;
219
220 retval = ext2fs_mmp_reset(fs);
221
222 return retval;
223 #else
224 return EXT2_ET_OP_NOT_SUPPORTED;
225 #endif
226 }
227
228 errcode_t ext2fs_mmp_init(ext2_filsys fs)
229 {
230 #ifdef CONFIG_MMP
231 struct ext2_super_block *sb = fs->super;
232 blk64_t mmp_block;
233 errcode_t retval;
234
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;
240
241 if (fs->mmp_buf == NULL) {
242 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
243 if (retval)
244 goto out;
245 }
246
247 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
248 if (retval)
249 goto out;
250
251 sb->s_mmp_block = mmp_block;
252
253 retval = ext2fs_mmp_reset(fs);
254 if (retval)
255 goto out;
256
257 out:
258 return retval;
259 #else
260 return EXT2_ET_OP_NOT_SUPPORTED;
261 #endif
262 }
263
264 /*
265 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
266 */
267 errcode_t ext2fs_mmp_start(ext2_filsys fs)
268 {
269 #ifdef CONFIG_MMP
270 struct mmp_struct *mmp_s;
271 unsigned seq;
272 unsigned int mmp_check_interval;
273 errcode_t retval = 0;
274
275 if (fs->mmp_buf == NULL) {
276 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
277 if (retval)
278 goto mmp_error;
279 }
280
281 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
282 if (retval)
283 goto mmp_error;
284
285 mmp_s = fs->mmp_buf;
286
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;
290
291 seq = mmp_s->mmp_seq;
292 if (seq == EXT4_MMP_SEQ_CLEAN)
293 goto clean_seq;
294 if (seq == EXT4_MMP_SEQ_FSCK) {
295 retval = EXT2_ET_MMP_FSCK_ON;
296 goto mmp_error;
297 }
298
299 if (seq > EXT4_MMP_SEQ_FSCK) {
300 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
301 goto mmp_error;
302 }
303
304 /*
305 * If check_interval in MMP block is larger, use that instead of
306 * check_interval from the superblock.
307 */
308 if (mmp_s->mmp_check_interval > mmp_check_interval)
309 mmp_check_interval = mmp_s->mmp_check_interval;
310
311 sleep(2 * mmp_check_interval + 1);
312
313 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
314 if (retval)
315 goto mmp_error;
316
317 if (seq != mmp_s->mmp_seq) {
318 retval = EXT2_ET_MMP_FAILED;
319 goto mmp_error;
320 }
321
322 clean_seq:
323 if (!(fs->flags & EXT2_FLAG_RW))
324 goto mmp_error;
325
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));
329 #else
330 strcpy(mmp_s->mmp_nodename, "unknown host");
331 #endif
332 strncpy(mmp_s->mmp_bdevname, fs->device_name,
333 sizeof(mmp_s->mmp_bdevname));
334
335 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
336 if (retval)
337 goto mmp_error;
338
339 sleep(2 * mmp_check_interval + 1);
340
341 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
342 if (retval)
343 goto mmp_error;
344
345 if (seq != mmp_s->mmp_seq) {
346 retval = EXT2_ET_MMP_FAILED;
347 goto mmp_error;
348 }
349
350 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
351 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
352 if (retval)
353 goto mmp_error;
354
355 return 0;
356
357 mmp_error:
358 return retval;
359 #else
360 return EXT2_ET_OP_NOT_SUPPORTED;
361 #endif
362 }
363
364 /*
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.
369 */
370 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
371 {
372 #ifdef CONFIG_MMP
373 struct mmp_struct *mmp, *mmp_cmp;
374 errcode_t retval = 0;
375
376 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
377 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
378 goto mmp_error;
379
380 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
381 if (retval)
382 goto mmp_error;
383
384 /* Check if the MMP block is not changed. */
385 mmp = fs->mmp_buf;
386 mmp_cmp = fs->mmp_cmp;
387 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
388 retval = EXT2_ET_MMP_CHANGE_ABORT;
389 goto mmp_error;
390 }
391
392 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
393 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
394
395 mmp_error:
396 if (fs->mmp_fd > 0) {
397 close(fs->mmp_fd);
398 fs->mmp_fd = -1;
399 }
400
401 return retval;
402 #else
403 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
404 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
405 return 0;
406
407 return EXT2_ET_OP_NOT_SUPPORTED;
408 #endif
409 }
410
411 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
412
413 /*
414 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
415 */
416 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
417 {
418 #ifdef CONFIG_MMP
419 struct mmp_struct *mmp, *mmp_cmp;
420 struct timeval tv;
421 errcode_t retval = 0;
422
423 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
424 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
425 return 0;
426
427 gettimeofday(&tv, 0);
428 if (!immediately &&
429 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
430 return 0;
431
432 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
433 if (retval)
434 goto mmp_error;
435
436 mmp = fs->mmp_buf;
437 mmp_cmp = fs->mmp_cmp;
438
439 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
440 return EXT2_ET_MMP_CHANGE_ABORT;
441
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);
445
446 mmp_error:
447 return retval;
448 #else
449 if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
450 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
451 return 0;
452
453 return EXT2_ET_OP_NOT_SUPPORTED;
454 #endif
455 }