Coverage Report

Created: 2026-03-18 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/e2fsprogs/lib/ext2fs/mmp.c
Line
Count
Source
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
#if __GNUC_PREREQ (4, 6)
38
#pragma GCC diagnostic push
39
#ifndef CONFIG_MMP
40
#pragma GCC diagnostic ignored "-Wunused-parameter"
41
#endif
42
#endif
43
44
errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
45
0
{
46
0
#ifdef CONFIG_MMP
47
0
  struct mmp_struct *mmp_cmp;
48
0
  errcode_t retval = 0;
49
50
0
  if ((mmp_blk <= fs->super->s_first_data_block) ||
51
0
      (mmp_blk >= ext2fs_blocks_count(fs->super)))
52
0
    return EXT2_ET_MMP_BAD_BLOCK;
53
54
  /* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
55
   * mmp_fd <= 0 is OK to validate that the fd is valid.  This opens its
56
   * own fd to read the MMP block to ensure that it is using O_DIRECT,
57
   * regardless of how the io_manager is doing reads, to avoid caching of
58
   * the MMP block by the io_manager or the VM.  It needs to be fresh. */
59
0
  if (fs->mmp_fd <= 0) {
60
0
    struct stat st;
61
0
    int flags = O_RDONLY | O_DIRECT;
62
63
    /*
64
     * There is no reason for using O_DIRECT if we're working with
65
     * regular file. Disabling it also avoids problems with
66
     * alignment when the device of the host file system has sector
67
     * size larger than blocksize of the fs we're working with.
68
     */
69
0
    if (stat(fs->device_name, &st) == 0 &&
70
0
        S_ISREG(st.st_mode))
71
0
      flags &= ~O_DIRECT;
72
73
0
    fs->mmp_fd = open(fs->device_name, flags);
74
0
    if (fs->mmp_fd < 0) {
75
0
      retval = EXT2_ET_MMP_OPEN_DIRECT;
76
0
      goto out;
77
0
    }
78
0
  }
79
80
0
  if (fs->mmp_cmp == NULL) {
81
0
    int align = ext2fs_get_dio_alignment(fs->mmp_fd);
82
83
0
    retval = ext2fs_get_memalign(fs->blocksize, align,
84
0
               &fs->mmp_cmp);
85
0
    if (retval)
86
0
      return retval;
87
0
  }
88
89
0
  if ((blk64_t) ext2fs_llseek(fs->mmp_fd, mmp_blk * fs->blocksize,
90
0
            SEEK_SET) !=
91
0
      mmp_blk * fs->blocksize) {
92
0
    retval = EXT2_ET_LLSEEK_FAILED;
93
0
    goto out;
94
0
  }
95
96
0
  if (read(fs->mmp_fd, fs->mmp_cmp, fs->blocksize) != fs->blocksize) {
97
0
    retval = EXT2_ET_SHORT_READ;
98
0
    goto out;
99
0
  }
100
101
0
  mmp_cmp = fs->mmp_cmp;
102
103
0
  if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
104
0
      !ext2fs_mmp_csum_verify(fs, mmp_cmp))
105
0
    retval = EXT2_ET_MMP_CSUM_INVALID;
106
107
#ifdef WORDS_BIGENDIAN
108
  ext2fs_swap_mmp(mmp_cmp);
109
#endif
110
111
0
  if (buf != NULL && buf != fs->mmp_cmp)
112
0
    memcpy(buf, fs->mmp_cmp, fs->blocksize);
113
114
0
  if (mmp_cmp->mmp_magic != EXT4_MMP_MAGIC) {
115
0
    retval = EXT2_ET_MMP_MAGIC_INVALID;
116
0
    goto out;
117
0
  }
118
119
0
out:
120
0
  return retval;
121
#else
122
  return EXT2_ET_OP_NOT_SUPPORTED;
123
#endif
124
0
}
125
126
errcode_t ext2fs_mmp_write(ext2_filsys fs, blk64_t mmp_blk, void *buf)
127
0
{
128
0
#ifdef CONFIG_MMP
129
0
  struct mmp_struct *mmp_s = buf;
130
0
  struct timeval tv;
131
0
  errcode_t retval = 0;
132
133
0
  gettimeofday(&tv, 0);
134
0
  mmp_s->mmp_time = tv.tv_sec;
135
0
  fs->mmp_last_written = tv.tv_sec;
136
137
0
  if (fs->super->s_mmp_block < fs->super->s_first_data_block ||
138
0
      fs->super->s_mmp_block > ext2fs_blocks_count(fs->super))
139
0
    return EXT2_ET_MMP_BAD_BLOCK;
140
141
#ifdef WORDS_BIGENDIAN
142
  ext2fs_swap_mmp(mmp_s);
143
#endif
144
145
0
  retval = ext2fs_mmp_csum_set(fs, mmp_s);
146
0
  if (retval)
147
0
    return retval;
148
149
  /* I was tempted to make this use O_DIRECT and the mmp_fd, but
150
   * this caused no end of grief, while leaving it as-is works. */
151
0
  retval = io_channel_write_blk64(fs->io, mmp_blk, -(int)sizeof(struct mmp_struct), buf);
152
153
#ifdef WORDS_BIGENDIAN
154
  ext2fs_swap_mmp(mmp_s);
155
#endif
156
157
  /* Make sure the block gets to disk quickly */
158
0
  io_channel_flush(fs->io);
159
0
  return retval;
160
#else
161
  return EXT2_ET_OP_NOT_SUPPORTED;
162
#endif
163
0
}
164
165
#ifdef HAVE_SRANDOM
166
0
#define srand(x)  srandom(x)
167
0
#define rand()    random()
168
#endif
169
170
unsigned ext2fs_mmp_new_seq(void)
171
0
{
172
0
#ifdef CONFIG_MMP
173
0
  unsigned new_seq;
174
0
  struct timeval tv;
175
0
  unsigned long pid = getpid();
176
177
0
  gettimeofday(&tv, 0);
178
0
  pid = (pid >> 16) | ((pid & 0xFFFF) << 16);
179
0
  srand(pid ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
180
181
0
  gettimeofday(&tv, 0);
182
  /* Crank the random number generator a few times */
183
0
  for (new_seq = (tv.tv_sec ^ tv.tv_usec) & 0x1F; new_seq > 0; new_seq--)
184
0
    rand();
185
186
0
  do {
187
0
    new_seq = rand();
188
0
  } while (new_seq > EXT4_MMP_SEQ_MAX);
189
190
0
  return new_seq;
191
#else
192
  return EXT2_ET_OP_NOT_SUPPORTED;
193
#endif
194
0
}
195
196
#ifdef CONFIG_MMP
197
static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
198
0
{
199
0
  struct mmp_struct *mmp_s = NULL;
200
0
  errcode_t retval = 0;
201
202
0
  if (fs->mmp_buf == NULL) {
203
0
    retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
204
0
    if (retval)
205
0
      goto out;
206
0
  }
207
208
0
  memset(fs->mmp_buf, 0, fs->blocksize);
209
0
  mmp_s = fs->mmp_buf;
210
211
0
  mmp_s->mmp_magic = EXT4_MMP_MAGIC;
212
0
  mmp_s->mmp_seq = EXT4_MMP_SEQ_CLEAN;
213
0
  mmp_s->mmp_time = 0;
214
0
#ifdef HAVE_GETHOSTNAME
215
0
  gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
216
#else
217
  mmp_s->mmp_nodename[0] = '\0';
218
#endif
219
0
  strncpy((char *) mmp_s->mmp_bdevname, fs->device_name,
220
0
    sizeof(mmp_s->mmp_bdevname));
221
222
0
  mmp_s->mmp_check_interval = fs->super->s_mmp_update_interval;
223
0
  if (mmp_s->mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
224
0
    mmp_s->mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
225
226
0
  retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
227
0
out:
228
0
  return retval;
229
0
}
230
#endif
231
232
errcode_t ext2fs_mmp_update(ext2_filsys fs)
233
0
{
234
0
  return ext2fs_mmp_update2(fs, 0);
235
0
}
236
237
errcode_t ext2fs_mmp_clear(ext2_filsys fs)
238
0
{
239
0
#ifdef CONFIG_MMP
240
0
  errcode_t retval = 0;
241
242
0
  if (!(fs->flags & EXT2_FLAG_RW))
243
0
    return EXT2_ET_RO_FILSYS;
244
245
0
  retval = ext2fs_mmp_reset(fs);
246
247
0
  return retval;
248
#else
249
  return EXT2_ET_OP_NOT_SUPPORTED;
250
#endif
251
0
}
252
253
errcode_t ext2fs_mmp_init(ext2_filsys fs)
254
0
{
255
0
#ifdef CONFIG_MMP
256
0
  struct ext2_super_block *sb = fs->super;
257
0
  blk64_t mmp_block;
258
0
  errcode_t retval;
259
260
0
  if (sb->s_mmp_update_interval == 0)
261
0
    sb->s_mmp_update_interval = EXT4_MMP_UPDATE_INTERVAL;
262
  /* This is probably excessively large, but who knows? */
263
0
  else if (sb->s_mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
264
0
    return EXT2_ET_INVALID_ARGUMENT;
265
266
0
  if (fs->mmp_buf == NULL) {
267
0
    retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
268
0
    if (retval)
269
0
      goto out;
270
0
  }
271
272
0
  retval = ext2fs_alloc_block2(fs, 0, fs->mmp_buf, &mmp_block);
273
0
  if (retval)
274
0
    goto out;
275
276
0
  sb->s_mmp_block = mmp_block;
277
278
0
  retval = ext2fs_mmp_reset(fs);
279
0
  if (retval)
280
0
    goto out;
281
282
0
out:
283
0
  return retval;
284
#else
285
  return EXT2_ET_OP_NOT_SUPPORTED;
286
#endif
287
0
}
288
289
#ifndef min
290
0
#define min(x, y) ((x) < (y) ? (x) : (y))
291
#endif
292
293
/*
294
 * Make sure that the fs is not mounted or being fsck'ed while opening the fs.
295
 */
296
errcode_t ext2fs_mmp_start(ext2_filsys fs)
297
0
{
298
0
#ifdef CONFIG_MMP
299
0
  struct mmp_struct *mmp_s;
300
0
  unsigned seq;
301
0
  unsigned int mmp_check_interval;
302
0
  errcode_t retval = 0;
303
304
0
  if (fs->mmp_buf == NULL) {
305
0
    retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
306
0
    if (retval)
307
0
      goto mmp_error;
308
0
  }
309
310
0
  retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
311
0
  if (retval)
312
0
    goto mmp_error;
313
314
0
  mmp_s = fs->mmp_buf;
315
316
0
  mmp_check_interval = fs->super->s_mmp_update_interval;
317
0
  if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
318
0
    mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
319
320
0
  seq = mmp_s->mmp_seq;
321
0
  if (seq == EXT4_MMP_SEQ_CLEAN)
322
0
    goto clean_seq;
323
0
  if (seq == EXT4_MMP_SEQ_FSCK) {
324
0
    retval = EXT2_ET_MMP_FSCK_ON;
325
0
    goto mmp_error;
326
0
  }
327
328
0
  if (seq > EXT4_MMP_SEQ_FSCK) {
329
0
    retval = EXT2_ET_MMP_UNKNOWN_SEQ;
330
0
    goto mmp_error;
331
0
  }
332
333
  /*
334
   * If check_interval in MMP block is larger, use that instead of
335
   * check_interval from the superblock.
336
   */
337
0
  if (mmp_s->mmp_check_interval > mmp_check_interval)
338
0
    mmp_check_interval = mmp_s->mmp_check_interval;
339
340
0
  sleep(min(mmp_check_interval * 2 + 1, mmp_check_interval + 60));
341
342
0
  retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
343
0
  if (retval)
344
0
    goto mmp_error;
345
346
0
  if (seq != mmp_s->mmp_seq) {
347
0
    retval = EXT2_ET_MMP_FAILED;
348
0
    goto mmp_error;
349
0
  }
350
351
0
clean_seq:
352
0
  if (!(fs->flags & EXT2_FLAG_RW))
353
0
    goto mmp_error;
354
355
0
  mmp_s->mmp_seq = seq = ext2fs_mmp_new_seq();
356
0
#ifdef HAVE_GETHOSTNAME
357
0
  gethostname((char *) mmp_s->mmp_nodename, sizeof(mmp_s->mmp_nodename));
358
#else
359
  strcpy((char *) mmp_s->mmp_nodename, "unknown host");
360
#endif
361
0
  strncpy((char *) mmp_s->mmp_bdevname, fs->device_name,
362
0
    sizeof(mmp_s->mmp_bdevname));
363
364
0
  retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
365
0
  if (retval)
366
0
    goto mmp_error;
367
368
0
  sleep(min(2 * mmp_check_interval + 1, mmp_check_interval + 60));
369
370
0
  retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
371
0
  if (retval)
372
0
    goto mmp_error;
373
374
0
  if (seq != mmp_s->mmp_seq) {
375
0
    retval = EXT2_ET_MMP_FAILED;
376
0
    goto mmp_error;
377
0
  }
378
379
0
  mmp_s->mmp_seq = EXT4_MMP_SEQ_FSCK;
380
0
  retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
381
0
  if (retval)
382
0
    goto mmp_error;
383
384
0
  return 0;
385
386
0
mmp_error:
387
0
  return retval;
388
#else
389
  return EXT2_ET_OP_NOT_SUPPORTED;
390
#endif
391
0
}
392
393
/*
394
 * Clear the MMP usage in the filesystem.  If this function returns an
395
 * error EXT2_ET_MMP_CHANGE_ABORT it means the filesystem was modified
396
 * by some other process while in use, and changes should be dropped, or
397
 * risk filesystem corruption.
398
 */
399
errcode_t ext2fs_mmp_stop(ext2_filsys fs)
400
300
{
401
300
#ifdef CONFIG_MMP
402
300
  struct mmp_struct *mmp, *mmp_cmp;
403
300
  errcode_t retval = 0;
404
405
300
  if (!ext2fs_has_feature_mmp(fs->super) ||
406
90
      !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP) ||
407
0
      (fs->mmp_buf == NULL) || (fs->mmp_cmp == NULL))
408
300
    goto mmp_error;
409
410
0
  retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
411
0
  if (retval)
412
0
    goto mmp_error;
413
414
  /* Check if the MMP block is not changed. */
415
0
  mmp = fs->mmp_buf;
416
0
  mmp_cmp = fs->mmp_cmp;
417
0
  if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp))) {
418
0
    retval = EXT2_ET_MMP_CHANGE_ABORT;
419
0
    goto mmp_error;
420
0
  }
421
422
0
  mmp_cmp->mmp_seq = EXT4_MMP_SEQ_CLEAN;
423
0
  retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
424
425
300
mmp_error:
426
300
  if (fs->mmp_fd > 0) {
427
0
    close(fs->mmp_fd);
428
0
    fs->mmp_fd = -1;
429
0
  }
430
431
300
  return retval;
432
#else
433
  if (!ext2fs_has_feature_mmp(fs->super) ||
434
      !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
435
    return 0;
436
437
  return EXT2_ET_OP_NOT_SUPPORTED;
438
#endif
439
0
}
440
441
0
#define EXT2_MIN_MMP_UPDATE_INTERVAL 60
442
443
/*
444
 * Update the on-disk mmp buffer, after checking that it hasn't been changed.
445
 */
446
errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
447
0
{
448
0
#ifdef CONFIG_MMP
449
0
  struct mmp_struct *mmp, *mmp_cmp;
450
0
  struct timeval tv;
451
0
  errcode_t retval = 0;
452
453
0
  if (!ext2fs_has_feature_mmp(fs->super) ||
454
0
      !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
455
0
    return 0;
456
457
0
  gettimeofday(&tv, 0);
458
0
  if (!immediately &&
459
0
      tv.tv_sec - fs->mmp_last_written < EXT2_MIN_MMP_UPDATE_INTERVAL)
460
0
    return 0;
461
462
0
  retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
463
0
  if (retval)
464
0
    goto mmp_error;
465
466
0
  mmp = fs->mmp_buf;
467
0
  mmp_cmp = fs->mmp_cmp;
468
469
0
  if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
470
0
    return EXT2_ET_MMP_CHANGE_ABORT;
471
472
0
  mmp->mmp_time = tv.tv_sec;
473
0
  mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
474
0
  retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
475
476
0
mmp_error:
477
0
  return retval;
478
#else
479
  if (!ext2fs_has_feature_mmp(fs->super) ||
480
      !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
481
    return 0;
482
483
  return EXT2_ET_OP_NOT_SUPPORTED;
484
#endif
485
0
}
486
#if __GNUC_PREREQ (4, 6)
487
#pragma GCC diagnostic pop
488
#endif