mmp.c revision ab00fdac7c5345d06d936640c12e1d24c7fdb6b2
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#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
30#ifndef O_DIRECT
31#define O_DIRECT 0
32#endif
33
34errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
35{
36	struct mmp_struct *mmp_cmp;
37	errcode_t retval = 0;
38
39	if ((mmp_blk <= fs->super->s_first_data_block) ||
40	    (mmp_blk >= fs->super->s_blocks_count))
41		return EXT2_ET_MMP_BAD_BLOCK;
42
43	/* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
44	 * mmp_fd <= 0 is OK to validate that the fd is valid.  This opens its
45	 * own fd to read the MMP block to ensure that it is using O_DIRECT,
46	 * regardless of how the io_manager is doing reads, to avoid caching of
47	 * the MMP block by the io_manager or the VM.  It needs to be fresh. */
48	if (fs->mmp_fd <= 0) {
49		fs->mmp_fd = open(fs->device_name, O_RDWR | O_DIRECT);
50		if (fs->mmp_fd < 0) {
51			retval = EXT2_ET_MMP_OPEN_DIRECT;
52			goto out;
53		}
54	}
55
56	if (fs->mmp_cmp == NULL) {
57		int align = ext2fs_get_dio_alignment(fs->mmp_fd);
58
59		retval = ext2fs_get_memalign(fs->blocksize, align,
60					     &fs->mmp_cmp);
61		if (retval)
62			return retval;
63	}
64
65	if (ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize, 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
89out:
90	return retval;
91}
92
93errcode_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
129unsigned ext2fs_mmp_new_seq()
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
149static 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);
179out:
180	return retval;
181}
182
183errcode_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
195errcode_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
223out:
224	return retval;
225}
226
227/*
228 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
229 */
230errcode_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
284clean_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
319mmp_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 */
329errcode_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
353mmp_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 */
367errcode_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
395mmp_error:
396	return retval;
397}
398