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