]> git.ipfire.org Git - thirdparty/e2fsprogs.git/blame - lib/ext2fs/mmp.c
libext2fs: include sys/time.h for gettimeofday()
[thirdparty/e2fsprogs.git] / lib / ext2fs / mmp.c
CommitLineData
0f5eba75
AD
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;
9026b3db
DW
94#ifdef WORDS_BIGENDIAN
95 ext2fs_swap_mmp(mmp_cmp);
0f5eba75
AD
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
9026b3db
DW
124#ifdef WORDS_BIGENDIAN
125 ext2fs_swap_mmp(mmp_s);
0f5eba75
AD
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. */
df7a86d4 130 retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
0f5eba75 131
9026b3db
DW
132#ifdef WORDS_BIGENDIAN
133 ext2fs_swap_mmp(mmp_s);
0f5eba75
AD
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}