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