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