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