Whamcloud - gitweb
libext2fs: fix punching extents when there are no left extents
[tools/e2fsprogs.git] / lib / ext2fs / punch.c
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
23 #undef PUNCH_DEBUG
24
25 /*
26  * This function returns 1 if the specified block is all zeros
27  */
28 static int check_zero_block(char *buf, int blocksize)
29 {
30         char    *cp = buf;
31         int     left = blocksize;
32
33         while (left > 0) {
34                 if (*cp++)
35                         return 0;
36                 left--;
37         }
38         return 1;
39 }
40
41 /*
42  * This clever recursive function handles i_blocks[] as well as
43  * indirect, double indirect, and triple indirect blocks.  It iterates
44  * over the entries in the i_blocks array or indirect blocks, and for
45  * each one, will recursively handle any indirect blocks and then
46  * frees and deallocates the blocks.
47  */
48 static errcode_t ind_punch(ext2_filsys fs, struct ext2_inode *inode,
49                            char *block_buf, blk_t *p, int level,
50                            blk_t start, blk_t count, int max)
51 {
52         errcode_t       retval;
53         blk_t           b;
54         int             i;
55         blk64_t         offset, incr;
56         int             freed = 0;
57
58 #ifdef PUNCH_DEBUG
59         printf("Entering ind_punch, level %d, start %u, count %u, "
60                "max %d\n", level, start, count, max);
61 #endif
62         incr = 1ULL << ((EXT2_BLOCK_SIZE_BITS(fs->super)-2)*level);
63         for (i=0, offset=0; i < max; i++, p++, offset += incr) {
64                 if (offset >= start + count)
65                         break;
66                 if (*p == 0 || (offset+incr) <= start)
67                         continue;
68                 b = *p;
69                 if (level > 0) {
70                         blk_t start2;
71 #ifdef PUNCH_DEBUG
72                         printf("Reading indirect block %u\n", b);
73 #endif
74                         retval = ext2fs_read_ind_block(fs, b, block_buf);
75                         if (retval)
76                                 return retval;
77                         start2 = (start > offset) ? start - offset : 0;
78                         retval = ind_punch(fs, inode, block_buf + fs->blocksize,
79                                            (blk_t *) block_buf, level - 1,
80                                            start2, count - offset,
81                                            fs->blocksize >> 2);
82                         if (retval)
83                                 return retval;
84                         retval = ext2fs_write_ind_block(fs, b, block_buf);
85                         if (retval)
86                                 return retval;
87                         if (!check_zero_block(block_buf, fs->blocksize))
88                                 continue;
89                 }
90 #ifdef PUNCH_DEBUG
91                 printf("Freeing block %u (offset %llu)\n", b, offset);
92 #endif
93                 ext2fs_block_alloc_stats(fs, b, -1);
94                 *p = 0;
95                 freed++;
96         }
97 #ifdef PUNCH_DEBUG
98         printf("Freed %d blocks\n", freed);
99 #endif
100         return ext2fs_iblk_sub_blocks(fs, inode, freed);
101 }
102
103 static errcode_t ext2fs_punch_ind(ext2_filsys fs, struct ext2_inode *inode,
104                                   char *block_buf, blk_t start, blk_t count)
105 {
106         errcode_t               retval;
107         char                    *buf = 0;
108         int                     level;
109         int                     num = EXT2_NDIR_BLOCKS;
110         blk_t                   *bp = inode->i_block;
111         blk_t                   addr_per_block;
112         blk64_t                 max = EXT2_NDIR_BLOCKS;
113
114         if (!block_buf) {
115                 retval = ext2fs_get_array(3, fs->blocksize, &buf);
116                 if (retval)
117                         return retval;
118                 block_buf = buf;
119         }
120
121         addr_per_block = (blk_t) fs->blocksize >> 2;
122
123         for (level = 0; level < 4; level++, max *= (blk64_t)addr_per_block) {
124 #ifdef PUNCH_DEBUG
125                 printf("Main loop level %d, start %u count %u "
126                        "max %llu num %d\n", level, start, count, max, num);
127 #endif
128                 if (start < max) {
129                         retval = ind_punch(fs, inode, block_buf, bp, level,
130                                            start, count, num);
131                         if (retval)
132                                 goto errout;
133                         if (count > max)
134                                 count -= max - start;
135                         else
136                                 break;
137                         start = 0;
138                 } else
139                         start -= max;
140                 bp += num;
141                 if (level == 0) {
142                         num = 1;
143                         max = 1;
144                 }
145         }
146         retval = 0;
147 errout:
148         if (buf)
149                 ext2fs_free_mem(&buf);
150         return retval;
151 }
152
153 #ifdef PUNCH_DEBUG
154
155 #define dbg_printf(f, a...)  printf(f, ## a)
156
157 static void dbg_print_extent(char *desc, struct ext2fs_extent *extent)
158 {
159         if (desc)
160                 printf("%s: ", desc);
161         printf("extent: lblk %llu--%llu, len %u, pblk %llu, flags: ",
162                extent->e_lblk, extent->e_lblk + extent->e_len - 1,
163                extent->e_len, extent->e_pblk);
164         if (extent->e_flags & EXT2_EXTENT_FLAGS_LEAF)
165                 fputs("LEAF ", stdout);
166         if (extent->e_flags & EXT2_EXTENT_FLAGS_UNINIT)
167                 fputs("UNINIT ", stdout);
168         if (extent->e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT)
169                 fputs("2ND_VISIT ", stdout);
170         if (!extent->e_flags)
171                 fputs("(none)", stdout);
172         fputc('\n', stdout);
173
174 }
175 #else
176 #define dbg_print_extent(desc, ex)      do { } while (0)
177 #define dbg_printf(f, a...)             do { } while (0)
178 #endif
179
180 static errcode_t ext2fs_punch_extent(ext2_filsys fs, ext2_ino_t ino,
181                                      struct ext2_inode *inode,
182                                      blk64_t start, blk64_t end)
183 {
184         ext2_extent_handle_t    handle = 0;
185         struct ext2fs_extent    extent;
186         errcode_t               retval;
187         blk64_t                 free_start, next;
188         __u32                   free_count, newlen;
189         int                     freed = 0;
190         int                     op;
191
192         retval = ext2fs_extent_open2(fs, ino, inode, &handle);
193         if (retval)
194                 return retval;
195         /*
196          * Find the extent closest to the start of the punch range.  We don't
197          * check the return value because _goto() sets the current node to the
198          * next-lowest extent if 'start' is in a hole, and doesn't set a
199          * current node if there was a real error reading the extent tree.
200          * In that case, _get() will error out.
201          */
202         ext2fs_extent_goto(handle, start);
203         retval = ext2fs_extent_get(handle, EXT2_EXTENT_CURRENT, &extent);
204         if (retval)
205                 goto errout;
206         while (1) {
207                 op = EXT2_EXTENT_NEXT_LEAF;
208                 dbg_print_extent("main loop", &extent);
209                 next = extent.e_lblk + extent.e_len;
210                 dbg_printf("start %llu, end %llu, next %llu\n",
211                            (unsigned long long) start,
212                            (unsigned long long) end,
213                            (unsigned long long) next);
214                 if (start <= extent.e_lblk) {
215                         if (end < extent.e_lblk)
216                                 goto next_extent;
217                         dbg_printf("Case #%d\n", 1);
218                         /* Start of deleted region before extent; 
219                            adjust beginning of extent */
220                         free_start = extent.e_pblk;
221                         if (next > end)
222                                 free_count = end - extent.e_lblk + 1;
223                         else
224                                 free_count = extent.e_len;
225                         extent.e_len -= free_count;
226                         extent.e_lblk += free_count;
227                         extent.e_pblk += free_count;
228                 } else if (end >= next-1) {
229                         if (start >= next)
230                                 break;
231                         /* End of deleted region after extent;
232                            adjust end of extent */
233                         dbg_printf("Case #%d\n", 2);
234                         newlen = start - extent.e_lblk;
235                         free_start = extent.e_pblk + newlen;
236                         free_count = extent.e_len - newlen;
237                         extent.e_len = newlen;
238                 } else {
239                         struct ext2fs_extent    newex;
240
241                         dbg_printf("Case #%d\n", 3);
242                         /* The hard case; we need to split the extent */
243                         newex.e_pblk = extent.e_pblk +
244                                 (end + 1 - extent.e_lblk);
245                         newex.e_lblk = end + 1;
246                         newex.e_len = next - end - 1;
247                         newex.e_flags = extent.e_flags;
248
249                         extent.e_len = start - extent.e_lblk;
250                         free_start = extent.e_pblk + extent.e_len;
251                         free_count = end - start + 1;
252
253                         dbg_print_extent("inserting", &newex);
254                         retval = ext2fs_extent_insert(handle,
255                                         EXT2_EXTENT_INSERT_AFTER, &newex);
256                         if (retval)
257                                 goto errout;
258                         /* Now pointing at inserted extent; so go back */
259                         retval = ext2fs_extent_get(handle,
260                                                    EXT2_EXTENT_PREV_LEAF,
261                                                    &newex);
262                         if (retval)
263                                 goto errout;
264                 } 
265                 if (extent.e_len) {
266                         dbg_print_extent("replacing", &extent);
267                         retval = ext2fs_extent_replace(handle, 0, &extent);
268                 } else {
269                         struct ext2fs_extent    newex;
270                         blk64_t                 old_lblk, next_lblk;
271                         dbg_printf("deleting current extent%s\n", "");
272
273                         /*
274                          * Save the location of the next leaf, then slip
275                          * back to the current extent.
276                          */
277                         retval = ext2fs_extent_get(handle, EXT2_EXTENT_CURRENT,
278                                                    &newex);
279                         if (retval)
280                                 goto errout;
281                         old_lblk = newex.e_lblk;
282
283                         retval = ext2fs_extent_get(handle,
284                                                    EXT2_EXTENT_NEXT_LEAF,
285                                                    &newex);
286                         if (retval == EXT2_ET_EXTENT_NO_NEXT)
287                                 next_lblk = old_lblk;
288                         else if (retval)
289                                 goto errout;
290                         else
291                                 next_lblk = newex.e_lblk;
292
293                         retval = ext2fs_extent_goto(handle, old_lblk);
294                         if (retval)
295                                 goto errout;
296
297                         /* Now delete the extent. */
298                         retval = ext2fs_extent_delete(handle, 0);
299                         if (retval)
300                                 goto errout;
301
302                         /* Jump forward to the next extent. */
303                         ext2fs_extent_goto(handle, next_lblk);
304                         op = EXT2_EXTENT_CURRENT;
305                 }
306                 if (retval)
307                         goto errout;
308                 dbg_printf("Free start %llu, free count = %u\n",
309                        free_start, free_count);
310                 while (free_count-- > 0) {
311                         ext2fs_block_alloc_stats2(fs, free_start++, -1);
312                         freed++;
313                 }
314         next_extent:
315                 retval = ext2fs_extent_get(handle, op,
316                                            &extent);
317                 if (retval == EXT2_ET_EXTENT_NO_NEXT ||
318                     retval == EXT2_ET_NO_CURRENT_NODE)
319                         break;
320                 if (retval)
321                         goto errout;
322         }
323         dbg_printf("Freed %d blocks\n", freed);
324         retval = ext2fs_iblk_sub_blocks(fs, inode, freed);
325 errout:
326         ext2fs_extent_free(handle);
327         return retval;
328 }
329         
330 /*
331  * Deallocate all logical blocks starting at start to end, inclusive.
332  * If end is ~0, then this is effectively truncate.
333  */
334 extern errcode_t ext2fs_punch(ext2_filsys fs, ext2_ino_t ino,
335                               struct ext2_inode *inode,
336                               char *block_buf, blk64_t start,
337                               blk64_t end)
338 {
339         errcode_t               retval;
340         struct ext2_inode       inode_buf;
341
342         if (start > end)
343                 return EINVAL;
344
345         /* Read inode structure if necessary */
346         if (!inode) {
347                 retval = ext2fs_read_inode(fs, ino, &inode_buf);
348                 if (retval)
349                         return retval;
350                 inode = &inode_buf;
351         }
352         if (inode->i_flags & EXT4_EXTENTS_FL)
353                 retval = ext2fs_punch_extent(fs, ino, inode, start, end);
354         else {
355                 blk_t   count;
356
357                 if (start > ~0U)
358                         return 0;
359                 count = ((end - start + 1) < ~0U) ? (end - start + 1) : ~0U;
360                 retval = ext2fs_punch_ind(fs, inode, block_buf, 
361                                           (blk_t) start, count);
362         }
363         if (retval)
364                 return retval;
365
366         return ext2fs_write_inode(fs, ino, inode);
367 }