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