Coverage Report

Created: 2026-03-07 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/e2fsprogs/lib/ext2fs/punch.c
Line
Count
Source
1
/*
2
 * punch.c --- deallocate blocks allocated to an inode
3
 *
4
 * Copyright (C) 2010 Theodore Ts'o.
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
#include "config.h"
13
#include <stdio.h>
14
#include <string.h>
15
#if HAVE_UNISTD_H
16
#include <unistd.h>
17
#endif
18
#include <errno.h>
19
20
#include "ext2_fs.h"
21
#include "ext2fs.h"
22
#include "ext2fsP.h"
23
24
#undef PUNCH_DEBUG
25
26
/*
27
 * This function returns 1 if the specified block is all zeros
28
 */
29
static int check_zero_block(char *buf, int blocksize)
30
0
{
31
0
  char  *cp = buf;
32
0
  int left = blocksize;
33
34
0
  while (left > 0) {
35
0
    if (*cp++)
36
0
      return 0;
37
0
    left--;
38
0
  }
39
0
  return 1;
40
0
}
41
42
/*
43
 * This clever recursive function handles i_blocks[] as well as
44
 * indirect, double indirect, and triple indirect blocks.  It iterates
45
 * over the entries in the i_blocks array or indirect blocks, and for
46
 * each one, will recursively handle any indirect blocks and then
47
 * frees and deallocates the blocks.
48
 */
49
static errcode_t ind_punch(ext2_filsys fs, struct ext2_inode *inode,
50
         char *block_buf, blk_t *p, int level,
51
         blk64_t start, blk64_t count, int max)
52
0
{
53
0
  errcode_t retval;
54
0
  blk_t   b;
55
0
  int   i;
56
0
  blk64_t   offset, incr;
57
0
  const blk64_t end = start + count;
58
0
  int   freed = 0;
59
60
#ifdef PUNCH_DEBUG
61
  printf("Entering ind_punch, level %d, start %llu, count %llu, "
62
         "max %d\n", level, start, count, max);
63
#endif
64
0
  incr = 1ULL << ((EXT2_BLOCK_SIZE_BITS(fs->super) - 2) * level);
65
0
  for (i = 0, offset = 0; i < max; i++, p++, offset += incr) {
66
0
    if (offset >= end)
67
0
      break;
68
0
    if (*p == 0 || (offset+incr) <= start)
69
0
      continue;
70
0
    b = *p;
71
0
    if (level > 0) {
72
0
      blk_t start2;
73
0
      blk_t count2;
74
#ifdef PUNCH_DEBUG
75
      printf("Reading indirect block %u\n", b);
76
#endif
77
0
      retval = ext2fs_read_ind_block(fs, b, block_buf);
78
0
      if (retval)
79
0
        return retval;
80
0
      start2 = (start > offset) ? start - offset : 0;
81
0
      count2 = end - ((start > offset) ? start : offset);
82
#ifdef PUNCH_DEBUG
83
      printf("start %llu offset %llu count %llu end %llu "
84
             "incr %llu start2 %u count2 %u\n", start,
85
             offset, count, end, incr, start2, count2);
86
#endif
87
0
      retval = ind_punch(fs, inode, block_buf + fs->blocksize,
88
0
             (blk_t *) block_buf, level - 1,
89
0
             start2, count2,
90
0
             fs->blocksize >> 2);
91
0
      if (retval)
92
0
        return retval;
93
0
      retval = ext2fs_write_ind_block(fs, b, block_buf);
94
0
      if (retval)
95
0
        return retval;
96
0
      if (!check_zero_block(block_buf, fs->blocksize))
97
0
        continue;
98
0
    }
99
#ifdef PUNCH_DEBUG
100
    printf("Freeing block %u (offset %llu)\n", b, offset);
101
#endif
102
0
    ext2fs_block_alloc_stats(fs, b, -1);
103
0
    *p = 0;
104
0
    freed++;
105
0
  }
106
#ifdef PUNCH_DEBUG
107
  printf("Freed %d blocks\n", freed);
108
#endif
109
0
  return ext2fs_iblk_sub_blocks(fs, inode, freed);
110
0
}
111
112
0
#define BLK_T_MAX ((blk_t)~0ULL)
113
static errcode_t ext2fs_punch_ind(ext2_filsys fs, struct ext2_inode *inode,
114
          char *block_buf, blk64_t start, blk64_t end)
115
0
{
116
0
  errcode_t   retval;
117
0
  char      *buf = 0;
118
0
  int     level;
119
0
  int     num = EXT2_NDIR_BLOCKS;
120
0
  blk_t     *bp = inode->i_block;
121
0
  blk_t     addr_per_block;
122
0
  blk64_t     max = EXT2_NDIR_BLOCKS;
123
0
  blk_t     count;
124
125
  /* Check start/end don't overflow the 2^32-1 indirect block limit */
126
0
  if (start > BLK_T_MAX)
127
0
    return 0;
128
0
  if (end >= BLK_T_MAX || end - start + 1 >= BLK_T_MAX)
129
0
    count = BLK_T_MAX - start;
130
0
  else
131
0
    count = end - start + 1;
132
133
0
  if (!block_buf) {
134
0
    retval = ext2fs_get_array(3, fs->blocksize, &buf);
135
0
    if (retval)
136
0
      return retval;
137
0
    block_buf = buf;
138
0
  }
139
140
0
  addr_per_block = (blk_t)fs->blocksize >> 2;
141
142
0
  for (level = 0; level < 4; level++, max *= (blk64_t)addr_per_block) {
143
#ifdef PUNCH_DEBUG
144
    printf("Main loop level %d, start %llu count %u "
145
           "max %llu num %d\n", level, start, count, max, num);
146
#endif
147
0
    if (start < max) {
148
0
      retval = ind_punch(fs, inode, block_buf, bp, level,
149
0
             start, count, num);
150
0
      if (retval)
151
0
        goto errout;
152
0
      if (count > max)
153
0
        count -= max - start;
154
0
      else
155
0
        break;
156
0
      start = 0;
157
0
    } else
158
0
      start -= max;
159
0
    bp += num;
160
0
    if (level == 0) {
161
0
      num = 1;
162
0
      max = 1;
163
0
    }
164
0
  }
165
0
  retval = 0;
166
0
errout:
167
0
  if (buf)
168
0
    ext2fs_free_mem(&buf);
169
0
  return retval;
170
0
}
171
#undef BLK_T_MAX
172
173
#ifdef PUNCH_DEBUG
174
175
#define dbg_printf(f, a...)  printf(f, ## a)
176
177
static void dbg_print_extent(char *desc, struct ext2fs_extent *extent)
178
{
179
  if (desc)
180
    printf("%s: ", desc);
181
  printf("extent: lblk %llu--%llu, len %u, pblk %llu, flags: ",
182
         extent->e_lblk, extent->e_lblk + extent->e_len - 1,
183
         extent->e_len, extent->e_pblk);
184
  if (extent->e_flags & EXT2_EXTENT_FLAGS_LEAF)
185
    fputs("LEAF ", stdout);
186
  if (extent->e_flags & EXT2_EXTENT_FLAGS_UNINIT)
187
    fputs("UNINIT ", stdout);
188
  if (extent->e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT)
189
    fputs("2ND_VISIT ", stdout);
190
  if (!extent->e_flags)
191
    fputs("(none)", stdout);
192
  fputc('\n', stdout);
193
194
}
195
#else
196
0
#define dbg_print_extent(desc, ex)  do { } while (0)
197
0
#define dbg_printf(f, a...)   do { } while (0)
198
#endif
199
200
/* Free a range of blocks, respecting cluster boundaries */
201
static errcode_t punch_extent_blocks(ext2_filsys fs, ext2_ino_t ino,
202
             struct ext2_inode *inode,
203
             blk64_t lfree_start, blk64_t free_start,
204
             __u32 free_count, blk64_t *freed)
205
0
{
206
0
  blk64_t   pblk;
207
0
  __u32   freed_now = 0;
208
0
  __u32   cluster_freed;
209
0
  errcode_t retval = 0;
210
211
0
  if (free_start < fs->super->s_first_data_block ||
212
0
      (free_start + free_count) > ext2fs_blocks_count(fs->super))
213
0
    return EXT2_ET_BAD_BLOCK_NUM;
214
215
  /* No bigalloc?  Just free each block. */
216
0
  if (EXT2FS_CLUSTER_RATIO(fs) == 1) {
217
0
    *freed += free_count;
218
0
    while (free_count-- > 0)
219
0
      ext2fs_block_alloc_stats2(fs, free_start++, -1);
220
0
    return retval;
221
0
  }
222
223
  /*
224
   * Try to free up to the next cluster boundary.  We assume that all
225
   * blocks in a logical cluster map to blocks from the same physical
226
   * cluster, and that the offsets within the [pl]clusters match.
227
   */
228
0
  if (free_start & EXT2FS_CLUSTER_MASK(fs)) {
229
0
    retval = ext2fs_map_cluster_block(fs, ino, inode,
230
0
              lfree_start, &pblk);
231
0
    if (retval)
232
0
      goto errout;
233
0
    if (!pblk) {
234
0
      ext2fs_block_alloc_stats2(fs, free_start, -1);
235
0
      freed_now++;
236
0
    }
237
0
    cluster_freed = EXT2FS_CLUSTER_RATIO(fs) -
238
0
      (free_start & EXT2FS_CLUSTER_MASK(fs));
239
0
    if (cluster_freed > free_count)
240
0
      cluster_freed = free_count;
241
0
    free_count -= cluster_freed;
242
0
    free_start += cluster_freed;
243
0
    lfree_start += cluster_freed;
244
0
  }
245
246
  /* Free whole clusters from the middle of the range. */
247
0
  while (free_count > 0 && free_count >= (unsigned) EXT2FS_CLUSTER_RATIO(fs)) {
248
0
    ext2fs_block_alloc_stats2(fs, free_start, -1);
249
0
    freed_now++;
250
0
    cluster_freed = EXT2FS_CLUSTER_RATIO(fs);
251
0
    free_count -= cluster_freed;
252
0
    free_start += cluster_freed;
253
0
    lfree_start += cluster_freed;
254
0
  }
255
256
  /* Try to free the last cluster. */
257
0
  if (free_count > 0) {
258
0
    retval = ext2fs_map_cluster_block(fs, ino, inode,
259
0
              lfree_start, &pblk);
260
0
    if (retval)
261
0
      goto errout;
262
0
    if (!pblk) {
263
0
      ext2fs_block_alloc_stats2(fs, free_start, -1);
264
0
      freed_now++;
265
0
    }
266
0
  }
267
268
0
errout:
269
0
  *freed += freed_now;
270
0
  return retval;
271
0
}
272
273
static errcode_t ext2fs_punch_extent(ext2_filsys fs, ext2_ino_t ino,
274
             struct ext2_inode *inode,
275
             blk64_t start, blk64_t end)
276
0
{
277
0
  ext2_extent_handle_t  handle = 0;
278
0
  struct ext2fs_extent  extent;
279
0
  errcode_t   retval;
280
0
  blk64_t     free_start, next, lfree_start;
281
0
  __u32     free_count, newlen;
282
0
  blk64_t     freed = 0;
283
0
  int     op;
284
285
0
  retval = ext2fs_extent_open2(fs, ino, inode, &handle);
286
0
  if (retval)
287
0
    return retval;
288
  /*
289
   * Find the extent closest to the start of the punch range.  We don't
290
   * check the return value because _goto() sets the current node to the
291
   * next-lowest extent if 'start' is in a hole, and doesn't set a
292
   * current node if there was a real error reading the extent tree.
293
   * In that case, _get() will error out.
294
   *
295
   * Note: If _get() returns 'no current node', that simply means that
296
   * there aren't any blocks mapped past this point in the file, so we're
297
   * done.
298
   */
299
0
  ext2fs_extent_goto(handle, start);
300
0
  retval = ext2fs_extent_get(handle, EXT2_EXTENT_CURRENT, &extent);
301
0
  if (retval == EXT2_ET_NO_CURRENT_NODE) {
302
0
    retval = 0;
303
0
    goto errout;
304
0
  } else if (retval)
305
0
    goto errout;
306
0
  while (1) {
307
0
    op = EXT2_EXTENT_NEXT_LEAF;
308
0
    dbg_print_extent("main loop", &extent);
309
0
    next = extent.e_lblk + extent.e_len;
310
0
    dbg_printf("start %llu, end %llu, next %llu\n",
311
0
         (unsigned long long) start,
312
0
         (unsigned long long) end,
313
0
         (unsigned long long) next);
314
0
    if (start <= extent.e_lblk) {
315
      /*
316
       * Have we iterated past the end of the punch region?
317
       * If so, we can stop.
318
       */
319
0
      if (end < extent.e_lblk)
320
0
        break;
321
0
      dbg_printf("Case #%d\n", 1);
322
      /* Start of deleted region before extent; 
323
         adjust beginning of extent */
324
0
      free_start = extent.e_pblk;
325
0
      lfree_start = extent.e_lblk;
326
0
      if (next > end)
327
0
        free_count = end - extent.e_lblk + 1;
328
0
      else
329
0
        free_count = extent.e_len;
330
0
      extent.e_len -= free_count;
331
0
      extent.e_lblk += free_count;
332
0
      extent.e_pblk += free_count;
333
0
    } else if (end >= next-1) {
334
      /*
335
       * Is the punch region beyond this extent?  This can
336
       * happen if start is already inside a hole.  Try to
337
       * advance to the next extent if this is the case.
338
       */
339
0
      if (start >= next)
340
0
        goto next_extent;
341
      /* End of deleted region after extent;
342
         adjust end of extent */
343
0
      dbg_printf("Case #%d\n", 2);
344
0
      newlen = start - extent.e_lblk;
345
0
      free_start = extent.e_pblk + newlen;
346
0
      lfree_start = extent.e_lblk + newlen;
347
0
      free_count = extent.e_len - newlen;
348
0
      extent.e_len = newlen;
349
0
    } else {
350
0
      struct ext2fs_extent  newex;
351
352
0
      dbg_printf("Case #%d\n", 3);
353
      /* The hard case; we need to split the extent */
354
0
      newex.e_pblk = extent.e_pblk +
355
0
        (end + 1 - extent.e_lblk);
356
0
      newex.e_lblk = end + 1;
357
0
      newex.e_len = next - end - 1;
358
0
      newex.e_flags = extent.e_flags;
359
360
0
      extent.e_len = start - extent.e_lblk;
361
0
      free_start = extent.e_pblk + extent.e_len;
362
0
      lfree_start = extent.e_lblk + extent.e_len;
363
0
      free_count = end - start + 1;
364
365
0
      dbg_print_extent("inserting", &newex);
366
0
      retval = ext2fs_extent_insert(handle,
367
0
          EXT2_EXTENT_INSERT_AFTER, &newex);
368
0
      if (retval)
369
0
        goto errout;
370
0
      retval = ext2fs_extent_fix_parents(handle);
371
0
      if (retval)
372
0
        goto errout;
373
      /*
374
       * Now pointing at inserted extent; so go back.
375
       *
376
       * We cannot use EXT2_EXTENT_PREV to go back; note the
377
       * subtlety in the comment for fix_parents().
378
       */
379
0
      retval = ext2fs_extent_goto(handle, extent.e_lblk);
380
0
      if (retval)
381
0
        goto errout;
382
0
    } 
383
0
    if (extent.e_len) {
384
0
      dbg_print_extent("replacing", &extent);
385
0
      retval = ext2fs_extent_replace(handle, 0, &extent);
386
0
      if (retval)
387
0
        goto errout;
388
0
      retval = ext2fs_extent_fix_parents(handle);
389
0
    } else {
390
0
      struct ext2fs_extent  newex;
391
0
      blk64_t     old_lblk, next_lblk;
392
0
      dbg_printf("deleting current extent%s\n", "");
393
394
      /*
395
       * Save the location of the next leaf, then slip
396
       * back to the current extent.
397
       */
398
0
      retval = ext2fs_extent_get(handle, EXT2_EXTENT_CURRENT,
399
0
               &newex);
400
0
      if (retval)
401
0
        goto errout;
402
0
      old_lblk = newex.e_lblk;
403
404
0
      retval = ext2fs_extent_get(handle,
405
0
               EXT2_EXTENT_NEXT_LEAF,
406
0
               &newex);
407
0
      if (retval == EXT2_ET_EXTENT_NO_NEXT)
408
0
        next_lblk = old_lblk;
409
0
      else if (retval)
410
0
        goto errout;
411
0
      else
412
0
        next_lblk = newex.e_lblk;
413
414
0
      retval = ext2fs_extent_goto(handle, old_lblk);
415
0
      if (retval)
416
0
        goto errout;
417
418
      /* Now delete the extent. */
419
0
      retval = ext2fs_extent_delete(handle, 0);
420
0
      if (retval)
421
0
        goto errout;
422
423
0
      retval = ext2fs_extent_fix_parents(handle);
424
0
      if (retval && retval != EXT2_ET_NO_CURRENT_NODE)
425
0
        goto errout;
426
0
      retval = 0;
427
428
      /*
429
       * Jump forward to the next extent.  If there are
430
       * errors, the ext2fs_extent_get down below will
431
       * capture them for us.
432
       */
433
0
      (void)ext2fs_extent_goto(handle, next_lblk);
434
0
      op = EXT2_EXTENT_CURRENT;
435
0
    }
436
0
    if (retval)
437
0
      goto errout;
438
0
    dbg_printf("Free start %llu, free count = %u\n",
439
0
           free_start, free_count);
440
0
    retval = punch_extent_blocks(fs, ino, inode, lfree_start,
441
0
               free_start, free_count, &freed);
442
0
    if (retval)
443
0
      goto errout;
444
0
  next_extent:
445
0
    retval = ext2fs_extent_get(handle, op,
446
0
             &extent);
447
0
    if (retval == EXT2_ET_EXTENT_NO_NEXT ||
448
0
        retval == EXT2_ET_NO_CURRENT_NODE)
449
0
      break;
450
0
    if (retval)
451
0
      goto errout;
452
0
  }
453
0
  dbg_printf("Freed %llu blocks\n", freed);
454
0
  retval = ext2fs_iblk_sub_blocks(fs, inode, freed);
455
0
errout:
456
0
  ext2fs_extent_free(handle);
457
0
  return retval;
458
0
}
459
  
460
static errcode_t ext2fs_punch_inline_data(ext2_filsys fs, ext2_ino_t ino,
461
            struct ext2_inode *inode,
462
            blk64_t start,
463
            blk64_t end EXT2FS_ATTR((unused)))
464
0
{
465
0
  errcode_t retval;
466
467
  /*
468
   * In libext2fs ext2fs_punch is based on block unit.  So that
469
   * means that if start > 0 we don't need to do nothing.  Due
470
   * to this we will remove all inline data in ext2fs_punch()
471
   * now.
472
   */
473
0
  if (start > 0)
474
0
    return 0;
475
476
0
  memset((char *)inode->i_block, 0, EXT4_MIN_INLINE_DATA_SIZE);
477
0
  inode->i_size = 0;
478
0
  retval = ext2fs_write_inode(fs, ino, inode);
479
0
  if (retval)
480
0
    return retval;
481
482
0
  return ext2fs_inline_data_ea_remove(fs, ino);
483
0
}
484
485
/*
486
 * Deallocate all logical _blocks_ starting at start to end, inclusive.
487
 * If end is ~0ULL, then this is effectively truncate.
488
 */
489
errcode_t ext2fs_punch(ext2_filsys fs, ext2_ino_t ino,
490
           struct ext2_inode *inode,
491
           char *block_buf, blk64_t start,
492
           blk64_t end)
493
0
{
494
0
  errcode_t   retval;
495
0
  struct ext2_inode inode_buf;
496
497
0
  if (start > end)
498
0
    return EINVAL;
499
500
  /* Read inode structure if necessary */
501
0
  if (!inode) {
502
0
    retval = ext2fs_read_inode(fs, ino, &inode_buf);
503
0
    if (retval)
504
0
      return retval;
505
0
    inode = &inode_buf;
506
0
  }
507
0
  if (inode->i_flags & EXT4_INLINE_DATA_FL)
508
0
    return ext2fs_punch_inline_data(fs, ino, inode, start, end);
509
0
  else if (inode->i_flags & EXT4_EXTENTS_FL)
510
0
    retval = ext2fs_punch_extent(fs, ino, inode, start, end);
511
0
  else
512
0
    retval = ext2fs_punch_ind(fs, inode, block_buf, start, end);
513
0
  if (retval)
514
0
    return retval;
515
516
#ifdef PUNCH_DEBUG
517
  printf("%u: write inode size now %lu blocks %u\n",
518
    ino, EXT2_I_SIZE(inode), inode->i_blocks);
519
#endif
520
0
  return ext2fs_write_inode(fs, ino, inode);
521
0
}