Coverage Report

Created: 2026-06-10 06:37

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