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