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