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