]> git.ipfire.org Git - thirdparty/e2fsprogs.git/blame - lib/ext2fs/mmp.c
mmp: fix 64-bit handling of s_mmp_block
[thirdparty/e2fsprogs.git] / lib / ext2fs / mmp.c
CommitLineData
0f5eba75
AD
1/*
2 * Helper functions for multiple mount protection (MMP).
3 *
4 * Copyright (C) 2011 Whamcloud, Inc.
5 *
6 * %Begin-Header%
ab00fdac
AD
7 * This file may be redistributed under the terms of the GNU Library
8 * General Public License, version 2.
0f5eba75
AD
9 * %End-Header%
10 */
11
12#ifndef _GNU_SOURCE
13#define _GNU_SOURCE
14#endif
15
997a0cf3
MF
16#include "config.h"
17
0f5eba75
AD
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
0f5eba75
AD
30#ifndef O_DIRECT
31#define O_DIRECT 0
32#endif
33
34errcode_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) ||
2fe2d408 40 (mmp_blk >= ext2fs_blocks_count(fs->super)))
0f5eba75
AD
41 return EXT2_ET_MMP_BAD_BLOCK;
42
0f5eba75
AD
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
dd0a2679
TT
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
e48bf256
TT
65 if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
66 SEEK_SET) !=
0f5eba75
AD
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;
9026b3db
DW
78#ifdef WORDS_BIGENDIAN
79 ext2fs_swap_mmp(mmp_cmp);
0f5eba75
AD
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
90out:
91 return retval;
92}
93
94errcode_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
9026b3db
DW
108#ifdef WORDS_BIGENDIAN
109 ext2fs_swap_mmp(mmp_s);
0f5eba75
AD
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. */
df7a86d4 114 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
0f5eba75 115
9026b3db
DW
116#ifdef WORDS_BIGENDIAN
117 ext2fs_swap_mmp(mmp_s);
0f5eba75
AD
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
130unsigned 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
150static 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);
180out:
181 return retval;
182}
183
184errcode_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
196errcode_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
224out:
225 return retval;
226}
227
228/*
229 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
230 */
231errcode_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
285clean_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
320mmp_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 */
330errcode_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
354mmp_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 */
368errcode_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
396mmp_error:
397 return retval;
398}