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