Whamcloud - gitweb
9a771de79929a65ad4a3cc453151616b32e23348
[tools/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 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
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 /*
273  * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
274  */
275 errcode_t ext2fs_mmp_start(ext2_filsys fs)
276 {
277 #ifdef CONFIG_MMP
278         struct mmp_struct *mmp_s;
279         unsigned seq;
280         unsigned int mmp_check_interval;
281         errcode_t retval = 0;
282
283         if (fs->mmp_buf == NULL) {
284                 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
285                 if (retval)
286                         goto mmp_error;
287         }
288
289         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
290         if (retval)
291                 goto mmp_error;
292
293         mmp_s = fs->mmp_buf;
294
295         mmp_check_interval = fs->super->s_mmp_update_interval;
296         if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
297                 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
298
299         seq = mmp_s->mmp_seq;
300         if (seq == EXT4_MMP_SEQ_CLEAN)
301                 goto clean_seq;
302         if (seq == EXT4_MMP_SEQ_FSCK) {
303                 retval = EXT2_ET_MMP_FSCK_ON;
304                 goto mmp_error;
305         }
306
307         if (seq > EXT4_MMP_SEQ_FSCK) {
308                 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
309                 goto mmp_error;
310         }
311
312         /*
313          * If check_interval in MMP block is larger, use that instead of
314          * check_interval from the superblock.
315          */
316         if (mmp_s->mmp_check_interval > mmp_check_interval)
317                 mmp_check_interval = mmp_s->mmp_check_interval;
318
319         sleep(2 * mmp_check_interval + 1);
320
321         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
322         if (retval)
323                 goto mmp_error;
324
325         if (seq != mmp_s->mmp_seq) {
326                 retval = EXT2_ET_MMP_FAILED;
327                 goto mmp_error;
328         }
329
330 clean_seq:
331         if (!(fs->flags & EXT2_FLAG_RW))
332                 goto mmp_error;
333
334         mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
335 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
336         gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
337 #else
338         strcpy(mmp_s->mmp_nodename, "unknown host");
339 #endif
340         strncpy(mmp_s->mmp_bdevname, fs->device_name,
341                 sizeof(mmp_s->mmp_bdevname));
342
343         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
344         if (retval)
345                 goto mmp_error;
346
347         sleep(2 * mmp_check_interval + 1);
348
349         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
350         if (retval)
351                 goto mmp_error;
352
353         if (seq != mmp_s->mmp_seq) {
354                 retval = EXT2_ET_MMP_FAILED;
355                 goto mmp_error;
356         }
357
358         mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
359         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
360         if (retval)
361                 goto mmp_error;
362
363         return 0;
364
365 mmp_error:
366         return retval;
367 #else
368         return EXT2_ET_OP_NOT_SUPPORTED;
369 #endif
370 }
371
372 /*
373  * Clear the MMP usage in the filesystem.  If this function returns an
374  * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
375  * by some other process while in use, and changes should be dropped, or
376  * risk filesystem corruption.
377  */
378 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
379 {
380 #ifdef CONFIG_MMP
381         struct mmp_struct *mmp, *mmp_cmp;
382         errcode_t retval = 0;
383
384         if (!ext2fs_has_feature_mmp(fs->super) ||
385             !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
386                 goto mmp_error;
387
388         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
389         if (retval)
390                 goto mmp_error;
391
392         /* Check if the MMP block is not changed. */
393         mmp = fs->mmp_buf;
394         mmp_cmp = fs->mmp_cmp;
395         if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
396                 retval = EXT2_ET_MMP_CHANGE_ABORT;
397                 goto mmp_error;
398         }
399
400         mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
401         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
402
403 mmp_error:
404         if (fs->mmp_fd > 0) {
405                 close(fs->mmp_fd);
406                 fs->mmp_fd = -1;
407         }
408
409         return retval;
410 #else
411         if (!ext2fs_has_feature_mmp(fs->super) ||
412             !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
413                 return 0;
414
415         return EXT2_ET_OP_NOT_SUPPORTED;
416 #endif
417 }
418
419 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
420
421 /*
422  * Update the on-disk mmp buffer, after checking that it hasn't been changed.
423  */
424 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
425 {
426 #ifdef CONFIG_MMP
427         struct mmp_struct *mmp, *mmp_cmp;
428         struct timeval tv;
429         errcode_t retval = 0;
430
431         if (!ext2fs_has_feature_mmp(fs->super) ||
432             !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
433                 return 0;
434
435         gettimeofday(&tv, 0);
436         if (!immediately &&
437             tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
438                 return 0;
439
440         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
441         if (retval)
442                 goto mmp_error;
443
444         mmp = fs->mmp_buf;
445         mmp_cmp = fs->mmp_cmp;
446
447         if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
448                 return EXT2_ET_MMP_CHANGE_ABORT;
449
450         mmp->mmp_time = tv.tv_sec;
451         mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
452         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
453
454 mmp_error:
455         return retval;
456 #else
457         if (!ext2fs_has_feature_mmp(fs->super) ||
458             !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
459                 return 0;
460
461         return EXT2_ET_OP_NOT_SUPPORTED;
462 #endif
463 }
464 #pragma GCC diagnostic pop