Whamcloud - gitweb
Merge branch 'maint' into next
[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
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 static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
172 {
173         struct mmp_struct *mmp_s = NULL;
174         errcode_t retval = 0;
175
176         if (fs->mmp_buf == NULL) {
177                 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
178                 if (retval)
179                         goto out;
180         }
181
182         memset(fs->mmp_buf, 0, fs->blocksize);
183         mmp_s = fs->mmp_buf;
184
185         mmp_s->mmp_magic = EXT4_MMP_MAGIC;
186         mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
187         mmp_s->mmp_time = 0;
188 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
189         gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
190 #else
191         mmp_s->mmp_nodename[0] = '\0';
192 #endif
193         strncpy(mmp_s->mmp_bdevname, fs->device_name,
194                 sizeof(mmp_s->mmp_bdevname));
195
196         mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
197         if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
198                 mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
199
200         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
201 out:
202         return retval;
203 }
204
205 errcode_t ext2fs_mmp_update(ext2_filsys fs)
206 {
207         return ext2fs_mmp_update2(fs, 0);
208 }
209
210 errcode_t ext2fs_mmp_clear(ext2_filsys fs)
211 {
212 #ifdef CONFIG_MMP
213         errcode_t retval = 0;
214
215         if (!(fs->flags & EXT2_FLAG_RW))
216                 return EXT2_ET_RO_FILSYS;
217
218         retval = ext2fs_mmp_reset(fs);
219
220         return retval;
221 #else
222         return EXT2_ET_OP_NOT_SUPPORTED;
223 #endif
224 }
225
226 errcode_t ext2fs_mmp_init(ext2_filsys fs)
227 {
228 #ifdef CONFIG_MMP
229         struct ext2_super_block *sb = fs->super;
230         blk64_t mmp_block;
231         errcode_t retval;
232
233         if (sb->s_mmp_update_interval == 0)
234                 sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
235         /* This is probably excessively large, but who knows? */
236         else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
237                 return EXT2_ET_INVALID_ARGUMENT;
238
239         if (fs->mmp_buf == NULL) {
240                 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
241                 if (retval)
242                         goto out;
243         }
244
245         retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
246         if (retval)
247                 goto out;
248
249         sb->s_mmp_block = mmp_block;
250
251         retval = ext2fs_mmp_reset(fs);
252         if (retval)
253                 goto out;
254
255 out:
256         return retval;
257 #else
258         return EXT2_ET_OP_NOT_SUPPORTED;
259 #endif
260 }
261
262 /*
263  * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
264  */
265 errcode_t ext2fs_mmp_start(ext2_filsys fs)
266 {
267 #ifdef CONFIG_MMP
268         struct mmp_struct *mmp_s;
269         unsigned seq;
270         unsigned int mmp_check_interval;
271         errcode_t retval = 0;
272
273         if (fs->mmp_buf == NULL) {
274                 retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
275                 if (retval)
276                         goto mmp_error;
277         }
278
279         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
280         if (retval)
281                 goto mmp_error;
282
283         mmp_s = fs->mmp_buf;
284
285         mmp_check_interval = fs->super->s_mmp_update_interval;
286         if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
287                 mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
288
289         seq = mmp_s->mmp_seq;
290         if (seq == EXT4_MMP_SEQ_CLEAN)
291                 goto clean_seq;
292         if (seq == EXT4_MMP_SEQ_FSCK) {
293                 retval = EXT2_ET_MMP_FSCK_ON;
294                 goto mmp_error;
295         }
296
297         if (seq > EXT4_MMP_SEQ_FSCK) {
298                 retval = EXT2_ET_MMP_UNKNOWN_SEQ;
299                 goto mmp_error;
300         }
301
302         /*
303          * If check_interval in MMP block is larger, use that instead of
304          * check_interval from the superblock.
305          */
306         if (mmp_s->mmp_check_interval > mmp_check_interval)
307                 mmp_check_interval = mmp_s->mmp_check_interval;
308
309         sleep(2 * mmp_check_interval + 1);
310
311         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
312         if (retval)
313                 goto mmp_error;
314
315         if (seq != mmp_s->mmp_seq) {
316                 retval = EXT2_ET_MMP_FAILED;
317                 goto mmp_error;
318         }
319
320 clean_seq:
321         if (!(fs->flags & EXT2_FLAG_RW))
322                 goto mmp_error;
323
324         mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
325 #if _BSD_SOURCE || _XOPEN_SOURCE >= 500
326         gethostname(mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
327 #else
328         strcpy(mmp_s->mmp_nodename, "unknown host");
329 #endif
330         strncpy(mmp_s->mmp_bdevname, fs->device_name,
331                 sizeof(mmp_s->mmp_bdevname));
332
333         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
334         if (retval)
335                 goto mmp_error;
336
337         sleep(2 * mmp_check_interval + 1);
338
339         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
340         if (retval)
341                 goto mmp_error;
342
343         if (seq != mmp_s->mmp_seq) {
344                 retval = EXT2_ET_MMP_FAILED;
345                 goto mmp_error;
346         }
347
348         mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
349         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
350         if (retval)
351                 goto mmp_error;
352
353         return 0;
354
355 mmp_error:
356         return retval;
357 #else
358         return EXT2_ET_OP_NOT_SUPPORTED;
359 #endif
360 }
361
362 /*
363  * Clear the MMP usage in the filesystem.  If this function returns an
364  * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
365  * by some other process while in use, and changes should be dropped, or
366  * risk filesystem corruption.
367  */
368 errcode_t ext2fs_mmp_stop(ext2_filsys fs)
369 {
370 #ifdef CONFIG_MMP
371         struct mmp_struct *mmp, *mmp_cmp;
372         errcode_t retval = 0;
373
374         if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
375             !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
376                 goto mmp_error;
377
378         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
379         if (retval)
380                 goto mmp_error;
381
382         /* Check if the MMP block is not changed. */
383         mmp = fs->mmp_buf;
384         mmp_cmp = fs->mmp_cmp;
385         if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
386                 retval = EXT2_ET_MMP_CHANGE_ABORT;
387                 goto mmp_error;
388         }
389
390         mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
391         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
392
393 mmp_error:
394         if (fs->mmp_fd > 0) {
395                 close(fs->mmp_fd);
396                 fs->mmp_fd = -1;
397         }
398
399         return retval;
400 #else
401         return EXT2_ET_OP_NOT_SUPPORTED;
402 #endif
403 }
404
405 #define EXT2_MIN_MMP_UPDATE_INTERVAL 60
406
407 /*
408  * Update the on-disk mmp buffer, after checking that it hasn't been changed.
409  */
410 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
411 {
412 #ifdef CONFIG_MMP
413         struct mmp_struct *mmp, *mmp_cmp;
414         struct timeval tv;
415         errcode_t retval = 0;
416
417         if (!(fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) ||
418             !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
419                 return 0;
420
421         gettimeofday(&tv, 0);
422         if (!immediately &&
423             tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
424                 return 0;
425
426         retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
427         if (retval)
428                 goto mmp_error;
429
430         mmp = fs->mmp_buf;
431         mmp_cmp = fs->mmp_cmp;
432
433         if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
434                 return EXT2_ET_MMP_CHANGE_ABORT;
435
436         mmp->mmp_time = tv.tv_sec;
437         mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
438         retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
439
440 mmp_error:
441         return retval;
442 #else
443         return EXT2_ET_OP_NOT_SUPPORTED;
444 #endif
445 }