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
42errcode_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
104out:
105	return retval;
106#else
107	return EXT2_ET_OP_NOT_SUPPORTED;
108#endif
109}
110
111errcode_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
155unsigned 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
180static 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);
210out:
211	return retval;
212}
213#endif
214
215errcode_t ext2fs_mmp_update(ext2_filsys fs)
216{
217	return ext2fs_mmp_update2(fs, 0);
218}
219
220errcode_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
236errcode_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
265out:
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 */
275errcode_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
330clean_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
365mmp_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 */
378errcode_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
403mmp_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 */
424errcode_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
454mmp_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