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