]> git.ipfire.org Git - thirdparty/e2fsprogs.git/blame - lib/ext2fs/mmp.c
ext4.5: add preprocessor hint
[thirdparty/e2fsprogs.git] / lib / ext2fs / mmp.c
CommitLineData
0f5eba75
AD
1/*
2 * Helper functions for multiple mount protection (MMP).
3 *
4 * Copyright (C) 2011 Whamcloud, Inc.
5 *
6 * %Begin-Header%
ab00fdac
AD
7 * This file may be redistributed under the terms of the GNU Library
8 * General Public License, version 2.
0f5eba75
AD
9 * %End-Header%
10 */
11
12#ifndef _GNU_SOURCE
13#define _GNU_SOURCE
14#endif
31ddef52
TT
15#ifndef _DEFAULT_SOURCE
16#define _DEFAULT_SOURCE /* since glibc 2.20 _SVID_SOURCE is deprecated */
17#endif
0f5eba75 18
997a0cf3
MF
19#include "config.h"
20
0f5eba75
AD
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
0f5eba75
AD
33#ifndef O_DIRECT
34#define O_DIRECT 0
35#endif
36
fddc423d 37#if __GNUC_PREREQ (4, 6)
25f291c9
TT
38#pragma GCC diagnostic push
39#ifndef CONFIG_MMP
40#pragma GCC diagnostic ignored "-Wunused-parameter"
41#endif
fddc423d 42#endif
25f291c9 43
0f5eba75
AD
44errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
45{
d6a4bcb5 46#ifdef CONFIG_MMP
0f5eba75
AD
47 struct mmp_struct *mmp_cmp;
48 errcode_t retval = 0;
49
50 if ((mmp_blk <= fs->super->s_first_data_block) ||
2fe2d408 51 (mmp_blk >= ext2fs_blocks_count(fs->super)))
0f5eba75
AD
52 return EXT2_ET_MMP_BAD_BLOCK;
53
0f5eba75
AD
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) {
76a6c878 60 struct stat st;
5cc83a2e 61 int flags = O_RDONLY | O_DIRECT;
947315c8 62
76a6c878
LC
63 /*
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.
68 */
69 if (stat(fs->device_name, &st) == 0 &&
70 S_ISREG(st.st_mode))
71 flags &= ~O_DIRECT;
72
947315c8 73 fs->mmp_fd = open(fs->device_name, flags);
0f5eba75
AD
74 if (fs->mmp_fd < 0) {
75 retval = EXT2_ET_MMP_OPEN_DIRECT;
76 goto out;
77 }
78 }
79
dd0a2679
TT
80 if (fs->mmp_cmp == NULL) {
81 int align = ext2fs_get_dio_alignment(fs->mmp_fd);
82
83 retval = ext2fs_get_memalign(fs->blocksize, align,
84 &fs->mmp_cmp);
85 if (retval)
86 return retval;
87 }
88
e48bf256
TT
89 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
90 SEEK_SET) !=
0f5eba75
AD
91 mmp_blk * fs->blocksize) {
92 retval = EXT2_ET_LLSEEK_FAILED;
93 goto out;
94 }
95
96 if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
97 retval = EXT2_ET_SHORT_READ;
98 goto out;
99 }
100
101 mmp_cmp = fs->mmp_cmp;
a9620d8b
DW
102
103 if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
104 !ext2fs_mmp_csum_verify(fs, mmp_cmp))
105 retval = EXT2_ET_MMP_CSUM_INVALID;
106
9026b3db
DW
107#ifdef WORDS_BIGENDIAN
108 ext2fs_swap_mmp(mmp_cmp);
0f5eba75
AD
109#endif
110
111 if (buf != NULL && buf != fs->mmp_cmp)
112 memcpy(buf, fs->mmp_cmp, fs->blocksize);
113
114 if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
115 retval = EXT2_ET_MMP_MAGIC_INVALID;
116 goto out;
117 }
118
119out:
120 return retval;
d6a4bcb5
TB
121#else
122 return EXT2_ET_OP_NOT_SUPPORTED;
123#endif
0f5eba75
AD
124}
125
126errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
127{
d6a4bcb5 128#ifdef CONFIG_MMP
0f5eba75
AD
129 struct mmp_struct *mmp_s = buf;
130 struct timeval tv;
131 errcode_t retval = 0;
132
133 gettimeofday(&tv, 0);
134 mmp_s->mmp_time = tv.tv_sec;
135 fs->mmp_last_written = tv.tv_sec;
136
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;
140
9026b3db
DW
141#ifdef WORDS_BIGENDIAN
142 ext2fs_swap_mmp(mmp_s);
0f5eba75
AD
143#endif
144
a9620d8b
DW
145 retval = ext2fs_mmp_csum_set(fs, mmp_s);
146 if (retval)
147 return retval;
148
0f5eba75
AD
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. */
df7a86d4 151 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
0f5eba75 152
9026b3db
DW
153#ifdef WORDS_BIGENDIAN
154 ext2fs_swap_mmp(mmp_s);
0f5eba75
AD
155#endif
156
157 /* Make sure the block gets to disk quickly */
158 io_channel_flush(fs->io);
159 return retval;
d6a4bcb5
TB
160#else
161 return EXT2_ET_OP_NOT_SUPPORTED;
162#endif
0f5eba75
AD
163}
164
165#ifdef HAVE_SRANDOM
166#define srand(x) srandom(x)
167#define rand() random()
168#endif
169
f404167d 170unsigned ext2fs_mmp_new_seq(void)
0f5eba75 171{
d6a4bcb5 172#ifdef CONFIG_MMP
0f5eba75
AD
173 unsigned new_seq;
174 struct timeval tv;
c3c41d4f 175 unsigned long pid = getpid();
0f5eba75
AD
176
177 gettimeofday(&tv, 0);
c3c41d4f
TT
178 pid = (pid >> 16) | ((pid & 0xFFFF) << 16);
179 srand(pid ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
0f5eba75
AD
180
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--)
184 rand();
185
186 do {
187 new_seq = rand();
188 } while (new_seq > EXT4_MMP_SEQ_MAX);
189
190 return new_seq;
d6a4bcb5
TB
191#else
192 return EXT2_ET_OP_NOT_SUPPORTED;
193#endif
0f5eba75
AD
194}
195
aee40b87 196#ifdef CONFIG_MMP
0f5eba75
AD
197static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
198{
199 struct mmp_struct *mmp_s = NULL;
200 errcode_t retval = 0;
201
202 if (fs->mmp_buf == NULL) {
203 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
204 if (retval)
205 goto out;
206 }
207
208 memset(fs->mmp_buf, 0, fs->blocksize);
209 mmp_s = fs->mmp_buf;
210
211 mmp_s->mmp_magic = EXT4_MMP_MAGIC;
212 mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
213 mmp_s->mmp_time = 0;
32b8802a 214#ifdef HAVE_GETHOSTNAME
b3f288ed 215 gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
0f5eba75
AD
216#else
217 mmp_s->mmp_nodename[0] = '\0';
218#endif
b3f288ed 219 strncpy((char *) mmp_s->mmp_bdevname, fs->device_name,
0f5eba75
AD
220 sizeof(mmp_s->mmp_bdevname));
221
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;
225
226 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
227out:
228 return retval;
229}
aee40b87 230#endif
0f5eba75 231
a9620d8b
DW
232errcode_t ext2fs_mmp_update(ext2_filsys fs)
233{
234 return ext2fs_mmp_update2(fs, 0);
235}
236
0f5eba75
AD
237errcode_t ext2fs_mmp_clear(ext2_filsys fs)
238{
d6a4bcb5 239#ifdef CONFIG_MMP
0f5eba75
AD
240 errcode_t retval = 0;
241
242 if (!(fs->flags & EXT2_FLAG_RW))
243 return EXT2_ET_RO_FILSYS;
244
245 retval = ext2fs_mmp_reset(fs);
246
247 return retval;
d6a4bcb5
TB
248#else
249 return EXT2_ET_OP_NOT_SUPPORTED;
250#endif
0f5eba75
AD
251}
252
253errcode_t ext2fs_mmp_init(ext2_filsys fs)
254{
d6a4bcb5 255#ifdef CONFIG_MMP
0f5eba75
AD
256 struct ext2_super_block *sb = fs->super;
257 blk64_t mmp_block;
258 errcode_t retval;
259
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;
265
266 if (fs->mmp_buf == NULL) {
267 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
268 if (retval)
269 goto out;
270 }
271
272 retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
273 if (retval)
274 goto out;
275
276 sb->s_mmp_block = mmp_block;
277
278 retval = ext2fs_mmp_reset(fs);
279 if (retval)
280 goto out;
281
282out:
283 return retval;
d6a4bcb5
TB
284#else
285 return EXT2_ET_OP_NOT_SUPPORTED;
286#endif
0f5eba75
AD
287}
288
32b8802a
SI
289#ifndef min
290#define min(x, y) ((x) < (y) ? (x) : (y))
291#endif
292
0f5eba75
AD
293/*
294 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
295 */
296errcode_t ext2fs_mmp_start(ext2_filsys fs)
297{
d6a4bcb5 298#ifdef CONFIG_MMP
0f5eba75
AD
299 struct mmp_struct *mmp_s;
300 unsigned seq;
301 unsigned int mmp_check_interval;
302 errcode_t retval = 0;
303
304 if (fs->mmp_buf == NULL) {
305 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
306 if (retval)
307 goto mmp_error;
308 }
309
310 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
311 if (retval)
312 goto mmp_error;
313
314 mmp_s = fs->mmp_buf;
315
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;
319
320 seq = mmp_s->mmp_seq;
321 if (seq == EXT4_MMP_SEQ_CLEAN)
322 goto clean_seq;
323 if (seq == EXT4_MMP_SEQ_FSCK) {
324 retval = EXT2_ET_MMP_FSCK_ON;
325 goto mmp_error;
326 }
327
328 if (seq > EXT4_MMP_SEQ_FSCK) {
329 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
330 goto mmp_error;
331 }
332
333 /*
334 * If check_interval in MMP block is larger, use that instead of
335 * check_interval from the superblock.
336 */
337 if (mmp_s->mmp_check_interval > mmp_check_interval)
338 mmp_check_interval = mmp_s->mmp_check_interval;
339
32b8802a 340 sleep(min(mmp_check_interval * 2 + 1, mmp_check_interval + 60));
0f5eba75
AD
341
342 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
343 if (retval)
344 goto mmp_error;
345
346 if (seq != mmp_s->mmp_seq) {
347 retval = EXT2_ET_MMP_FAILED;
348 goto mmp_error;
349 }
350
351clean_seq:
352 if (!(fs->flags & EXT2_FLAG_RW))
353 goto mmp_error;
354
355 mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
32b8802a 356#ifdef HAVE_GETHOSTNAME
b3f288ed 357 gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
0f5eba75 358#else
ca347785 359 strcpy((char *) mmp_s->mmp_nodename, "unknown host");
0f5eba75 360#endif
b3f288ed 361 strncpy((char *) mmp_s->mmp_bdevname, fs->device_name,
0f5eba75
AD
362 sizeof(mmp_s->mmp_bdevname));
363
364 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
365 if (retval)
366 goto mmp_error;
367
32b8802a 368 sleep(min(2 * mmp_check_interval + 1, mmp_check_interval + 60));
0f5eba75
AD
369
370 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
371 if (retval)
372 goto mmp_error;
373
374 if (seq != mmp_s->mmp_seq) {
375 retval = EXT2_ET_MMP_FAILED;
376 goto mmp_error;
377 }
378
379 mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
380 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
381 if (retval)
382 goto mmp_error;
383
384 return 0;
385
386mmp_error:
387 return retval;
d6a4bcb5
TB
388#else
389 return EXT2_ET_OP_NOT_SUPPORTED;
390#endif
0f5eba75
AD
391}
392
393/*
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.
398 */
399errcode_t ext2fs_mmp_stop(ext2_filsys fs)
400{
d6a4bcb5 401#ifdef CONFIG_MMP
0f5eba75
AD
402 struct mmp_struct *mmp, *mmp_cmp;
403 errcode_t retval = 0;
404
77b3e987 405 if (!ext2fs_has_feature_mmp(fs->super) ||
71f9bf7b
TT
406 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP) ||
407 (fs->mmp_buf == NULL) || (fs->mmp_cmp == NULL))
0f5eba75
AD
408 goto mmp_error;
409
ffa6de1e 410 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
0f5eba75
AD
411 if (retval)
412 goto mmp_error;
413
414 /* Check if the MMP block is not changed. */
415 mmp = fs->mmp_buf;
416 mmp_cmp = fs->mmp_cmp;
417 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
418 retval = EXT2_ET_MMP_CHANGE_ABORT;
419 goto mmp_error;
420 }
421
422 mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
423 retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
424
425mmp_error:
426 if (fs->mmp_fd > 0) {
427 close(fs->mmp_fd);
428 fs->mmp_fd = -1;
429 }
430
431 return retval;
d6a4bcb5 432#else
77b3e987 433 if (!ext2fs_has_feature_mmp(fs->super) ||
766c1428
TT
434 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
435 return 0;
436
d6a4bcb5
TB
437 return EXT2_ET_OP_NOT_SUPPORTED;
438#endif
0f5eba75
AD
439}
440
441#define EXT2_MIN_MMP_UPDATE_INTERVAL 60
442
443/*
444 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
445 */
a9620d8b 446errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
0f5eba75 447{
d6a4bcb5 448#ifdef CONFIG_MMP
0f5eba75
AD
449 struct mmp_struct *mmp, *mmp_cmp;
450 struct timeval tv;
451 errcode_t retval = 0;
452
77b3e987 453 if (!ext2fs_has_feature_mmp(fs->super) ||
0f5eba75
AD
454 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
455 return 0;
456
457 gettimeofday(&tv, 0);
a9620d8b
DW
458 if (!immediately &&
459 tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
0f5eba75
AD
460 return 0;
461
462 retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
463 if (retval)
464 goto mmp_error;
465
466 mmp = fs->mmp_buf;
467 mmp_cmp = fs->mmp_cmp;
468
469 if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
470 return EXT2_ET_MMP_CHANGE_ABORT;
471
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);
475
476mmp_error:
477 return retval;
d6a4bcb5 478#else
77b3e987 479 if (!ext2fs_has_feature_mmp(fs->super) ||
766c1428
TT
480 !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
481 return 0;
482
d6a4bcb5
TB
483 return EXT2_ET_OP_NOT_SUPPORTED;
484#endif
0f5eba75 485}
fddc423d 486#if __GNUC_PREREQ (4, 6)
25f291c9 487#pragma GCC diagnostic pop
fddc423d 488#endif