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