/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 | } |