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 
16 
17 #if HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #include <sys/time.h>
21 
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 
26 #include "ext2fs/ext2_fs.h"
27 #include "ext2fs/ext2fs.h"
28 
29 #ifndef O_DIRECT
30 #define O_DIRECT 0
31 #endif
32 
ext2fs_mmp_read(ext2_filsys fs,blk64_t mmp_blk,void * buf)33 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
34 {
35 	struct mmp_struct *mmp_cmp;
36 	errcode_t retval = 0;
37 
38 	if ((mmp_blk <= fs->super->s_first_data_block) ||
39 	    (mmp_blk >= ext2fs_blocks_count(fs->super)))
40 		return EXT2_ET_MMP_BAD_BLOCK;
41 
42 	/* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
43 	 * mmp_fd <= 0 is OK to validate that the fd is valid.  This opens its
44 	 * own fd to read the MMP block to ensure that it is using O_DIRECT,
45 	 * regardless of how the io_manager is doing reads, to avoid caching of
46 	 * the MMP block by the io_manager or the VM.  It needs to be fresh. */
47 	if (fs->mmp_fd <= 0) {
48 		fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
49 		if (fs->mmp_fd < 0) {
50 			retval = EXT2_ET_MMP_OPEN_DIRECT;
51 			goto out;
52 		}
53 	}
54 
55 	if (fs->mmp_cmp == NULL) {
56 		int align = ext2fs_get_dio_alignment(fs->mmp_fd);
57 
58 		retval = ext2fs_get_memalign(fs->blocksize, align,
59 					     &fs->mmp_cmp);
60 		if (retval)
61 			return retval;
62 	}
63 
64 	if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
65 				    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 
ext2fs_mmp_write(ext2_filsys fs,blk64_t mmp_blk,void * buf)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 
ext2fs_mmp_new_seq(void)129 unsigned ext2fs_mmp_new_seq(void)
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 
ext2fs_mmp_reset(ext2_filsys fs)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 
ext2fs_mmp_clear(ext2_filsys fs)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 
ext2fs_mmp_init(ext2_filsys fs)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  */
ext2fs_mmp_start(ext2_filsys fs)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  */
ext2fs_mmp_stop(ext2_filsys fs)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  */
ext2fs_mmp_update(ext2_filsys fs)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 }
398