gfs2: Generalize truncate code
[sfrench/cifs-2.6.git] / fs / gfs2 / bmap.c
index d5f0d96169c53eddacef8bec504002375cab3bda..8fd42ae026dd64b03be4b6d51d8538936fcd7f80 100644 (file)
@@ -279,14 +279,13 @@ static inline __be64 *metapointer(unsigned int height, const struct metapath *mp
        return p + mp->mp_list[height];
 }
 
-static void gfs2_metapath_ra(struct gfs2_glock *gl,
-                            const struct buffer_head *bh, const __be64 *pos)
+static void gfs2_metapath_ra(struct gfs2_glock *gl, __be64 *start, __be64 *end)
 {
-       struct buffer_head *rabh;
-       const __be64 *endp = (const __be64 *)(bh->b_data + bh->b_size);
        const __be64 *t;
 
-       for (t = pos; t < endp; t++) {
+       for (t = start; t < end; t++) {
+               struct buffer_head *rabh;
+
                if (!*t)
                        continue;
 
@@ -305,21 +304,22 @@ static void gfs2_metapath_ra(struct gfs2_glock *gl,
        }
 }
 
-/**
- * lookup_mp_height - helper function for lookup_metapath
- * @ip: the inode
- * @mp: the metapath
- * @h: the height which needs looking up
- */
-static int lookup_mp_height(struct gfs2_inode *ip, struct metapath *mp, int h)
+static int __fillup_metapath(struct gfs2_inode *ip, struct metapath *mp,
+                            unsigned int x, unsigned int h)
 {
-       __be64 *ptr = metapointer(h, mp);
-       u64 dblock = be64_to_cpu(*ptr);
-
-       if (!dblock)
-               return h + 1;
+       for (; x < h; x++) {
+               __be64 *ptr = metapointer(x, mp);
+               u64 dblock = be64_to_cpu(*ptr);
+               int ret;
 
-       return gfs2_meta_indirect_buffer(ip, h + 1, dblock, &mp->mp_bh[h + 1]);
+               if (!dblock)
+                       break;
+               ret = gfs2_meta_indirect_buffer(ip, x + 1, dblock, &mp->mp_bh[x + 1]);
+               if (ret)
+                       return ret;
+       }
+       mp->mp_aheight = x + 1;
+       return 0;
 }
 
 /**
@@ -336,25 +336,12 @@ static int lookup_mp_height(struct gfs2_inode *ip, struct metapath *mp, int h)
  * at which it found the unallocated block. Blocks which are found are
  * added to the mp->mp_bh[] list.
  *
- * Returns: error or height of metadata tree
+ * Returns: error
  */
 
 static int lookup_metapath(struct gfs2_inode *ip, struct metapath *mp)
 {
-       unsigned int end_of_metadata = ip->i_height - 1;
-       unsigned int x;
-       int ret;
-
-       for (x = 0; x < end_of_metadata; x++) {
-               ret = lookup_mp_height(ip, mp, x);
-               if (ret)
-                       goto out;
-       }
-
-       ret = ip->i_height;
-out:
-       mp->mp_aheight = ret;
-       return ret;
+       return __fillup_metapath(ip, mp, 0, ip->i_height - 1);
 }
 
 /**
@@ -365,25 +352,25 @@ out:
  *
  * Similar to lookup_metapath, but does lookups for a range of heights
  *
- * Returns: error or height of metadata tree
+ * Returns: error or the number of buffers filled
  */
 
 static int fillup_metapath(struct gfs2_inode *ip, struct metapath *mp, int h)
 {
-       unsigned int start_h = h - 1;
+       unsigned int x = 0;
        int ret;
 
        if (h) {
                /* find the first buffer we need to look up. */
-               while (start_h > 0 && mp->mp_bh[start_h] == NULL)
-                       start_h--;
-               for (; start_h < h; start_h++) {
-                       ret = lookup_mp_height(ip, mp, start_h);
-                       if (ret)
-                               return ret;
+               for (x = h - 1; x > 0; x--) {
+                       if (mp->mp_bh[x])
+                               break;
                }
        }
-       return ip->i_height;
+       ret = __fillup_metapath(ip, mp, x, h);
+       if (ret)
+               return ret;
+       return mp->mp_aheight - x - 1;
 }
 
 static inline void release_metapath(struct metapath *mp)
@@ -788,7 +775,7 @@ int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length,
                goto do_alloc;
 
        ret = lookup_metapath(ip, &mp);
-       if (ret < 0)
+       if (ret)
                goto out_release;
 
        if (mp.mp_aheight != ip->i_height)
@@ -913,17 +900,18 @@ int gfs2_extent_map(struct inode *inode, u64 lblock, int *new, u64 *dblock, unsi
 }
 
 /**
- * gfs2_block_truncate_page - Deal with zeroing out data for truncate
+ * gfs2_block_zero_range - Deal with zeroing out data
  *
  * This is partly borrowed from ext3.
  */
-static int gfs2_block_truncate_page(struct address_space *mapping, loff_t from)
+static int gfs2_block_zero_range(struct inode *inode, loff_t from,
+                                unsigned int length)
 {
-       struct inode *inode = mapping->host;
+       struct address_space *mapping = inode->i_mapping;
        struct gfs2_inode *ip = GFS2_I(inode);
        unsigned long index = from >> PAGE_SHIFT;
        unsigned offset = from & (PAGE_SIZE-1);
-       unsigned blocksize, iblock, length, pos;
+       unsigned blocksize, iblock, pos;
        struct buffer_head *bh;
        struct page *page;
        int err;
@@ -933,7 +921,6 @@ static int gfs2_block_truncate_page(struct address_space *mapping, loff_t from)
                return 0;
 
        blocksize = inode->i_sb->s_blocksize;
-       length = blocksize - (offset & (blocksize - 1));
        iblock = index << (PAGE_SHIFT - inode->i_sb->s_blocksize_bits);
 
        if (!page_has_buffers(page))
@@ -1003,11 +990,24 @@ static int gfs2_journaled_truncate(struct inode *inode, u64 oldsize, u64 newsize
        int error;
 
        while (oldsize != newsize) {
+               struct gfs2_trans *tr;
+               unsigned int offs;
+
                chunk = oldsize - newsize;
                if (chunk > max_chunk)
                        chunk = max_chunk;
+
+               offs = oldsize & ~PAGE_MASK;
+               if (offs && chunk > PAGE_SIZE)
+                       chunk = offs + ((chunk - offs) & PAGE_MASK);
+
                truncate_pagecache(inode, oldsize - chunk);
                oldsize -= chunk;
+
+               tr = current->journal_info;
+               if (!test_bit(TR_TOUCHED, &tr->tr_flags))
+                       continue;
+
                gfs2_trans_end(sdp);
                error = gfs2_trans_begin(sdp, RES_DINODE, GFS2_JTRUNC_REVOKES);
                if (error)
@@ -1017,13 +1017,13 @@ static int gfs2_journaled_truncate(struct inode *inode, u64 oldsize, u64 newsize
        return 0;
 }
 
-static int trunc_start(struct inode *inode, u64 oldsize, u64 newsize)
+static int trunc_start(struct inode *inode, u64 newsize)
 {
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_sbd *sdp = GFS2_SB(inode);
-       struct address_space *mapping = inode->i_mapping;
-       struct buffer_head *dibh;
+       struct buffer_head *dibh = NULL;
        int journaled = gfs2_is_jdata(ip);
+       u64 oldsize = inode->i_size;
        int error;
 
        if (journaled)
@@ -1042,10 +1042,13 @@ static int trunc_start(struct inode *inode, u64 oldsize, u64 newsize)
        if (gfs2_is_stuffed(ip)) {
                gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode) + newsize);
        } else {
-               if (newsize & (u64)(sdp->sd_sb.sb_bsize - 1)) {
-                       error = gfs2_block_truncate_page(mapping, newsize);
+               unsigned int blocksize = i_blocksize(inode);
+               unsigned int offs = newsize & (blocksize - 1);
+               if (offs) {
+                       error = gfs2_block_zero_range(inode, newsize,
+                                                     blocksize - offs);
                        if (error)
-                               goto out_brelse;
+                               goto out;
                }
                ip->i_diskflags |= GFS2_DIF_TRUNC_IN_PROG;
        }
@@ -1059,15 +1062,10 @@ static int trunc_start(struct inode *inode, u64 oldsize, u64 newsize)
        else
                truncate_pagecache(inode, newsize);
 
-       if (error) {
-               brelse(dibh);
-               return error;
-       }
-
-out_brelse:
-       brelse(dibh);
 out:
-       gfs2_trans_end(sdp);
+       brelse(dibh);
+       if (current->journal_info)
+               gfs2_trans_end(sdp);
        return error;
 }
 
@@ -1075,10 +1073,11 @@ out:
  * sweep_bh_for_rgrps - find an rgrp in a meta buffer and free blocks therein
  * @ip: inode
  * @rg_gh: holder of resource group glock
- * @mp: current metapath fully populated with buffers
+ * @bh: buffer head to sweep
+ * @start: starting point in bh
+ * @end: end point in bh
+ * @meta: true if bh points to metadata (rather than data)
  * @btotal: place to keep count of total blocks freed
- * @hgt: height we're processing
- * @first: true if this is the first call to this function for this height
  *
  * We sweep a metadata buffer (provided by the metapath) for blocks we need to
  * free, and free them all. However, we do it one rgrp at a time. If this
@@ -1093,47 +1092,46 @@ out:
  *          *btotal has the total number of blocks freed
  */
 static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh,
-                             const struct metapath *mp, u32 *btotal, int hgt,
-                             bool preserve1)
+                             struct buffer_head *bh, __be64 *start, __be64 *end,
+                             bool meta, u32 *btotal)
 {
        struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode);
        struct gfs2_rgrpd *rgd;
        struct gfs2_trans *tr;
-       struct buffer_head *bh = mp->mp_bh[hgt];
-       __be64 *top, *bottom, *p;
+       __be64 *p;
        int blks_outside_rgrp;
        u64 bn, bstart, isize_blks;
        s64 blen; /* needs to be s64 or gfs2_add_inode_blocks breaks */
-       int meta = ((hgt != ip->i_height - 1) ? 1 : 0);
        int ret = 0;
        bool buf_in_tr = false; /* buffer was added to transaction */
 
-       if (gfs2_metatype_check(sdp, bh,
-                               (hgt ? GFS2_METATYPE_IN : GFS2_METATYPE_DI)))
-               return -EIO;
-
 more_rgrps:
+       rgd = NULL;
+       if (gfs2_holder_initialized(rd_gh)) {
+               rgd = gfs2_glock2rgrp(rd_gh->gh_gl);
+               gfs2_assert_withdraw(sdp,
+                            gfs2_glock_is_locked_by_me(rd_gh->gh_gl));
+       }
        blks_outside_rgrp = 0;
        bstart = 0;
        blen = 0;
-       top = metapointer(hgt, mp); /* first ptr from metapath */
-       /* If we're keeping some data at the truncation point, we've got to
-          preserve the metadata tree by adding 1 to the starting metapath. */
-       if (preserve1)
-               top++;
-
-       bottom = (__be64 *)(bh->b_data + bh->b_size);
 
-       for (p = top; p < bottom; p++) {
+       for (p = start; p < end; p++) {
                if (!*p)
                        continue;
                bn = be64_to_cpu(*p);
-               if (gfs2_holder_initialized(rd_gh)) {
-                       rgd = gfs2_glock2rgrp(rd_gh->gh_gl);
-                       gfs2_assert_withdraw(sdp,
-                                    gfs2_glock_is_locked_by_me(rd_gh->gh_gl));
+
+               if (rgd) {
+                       if (!rgrp_contains_block(rgd, bn)) {
+                               blks_outside_rgrp++;
+                               continue;
+                       }
                } else {
-                       rgd = gfs2_blk2rgrpd(sdp, bn, false);
+                       rgd = gfs2_blk2rgrpd(sdp, bn, true);
+                       if (unlikely(!rgd)) {
+                               ret = -EIO;
+                               goto out;
+                       }
                        ret = gfs2_glock_nq_init(rgd->rd_gl, LM_ST_EXCLUSIVE,
                                                 0, rd_gh);
                        if (ret)
@@ -1145,11 +1143,6 @@ more_rgrps:
                                gfs2_rs_deltree(&ip->i_res);
                }
 
-               if (!rgrp_contains_block(rgd, bn)) {
-                       blks_outside_rgrp++;
-                       continue;
-               }
-
                /* The size of our transactions will be unknown until we
                   actually process all the metadata blocks that relate to
                   the rgrp. So we estimate. We know it can't be more than
@@ -1168,7 +1161,7 @@ more_rgrps:
                                jblocks_rqsted += isize_blks;
                        revokes = jblocks_rqsted;
                        if (meta)
-                               revokes += hptrs(sdp, hgt);
+                               revokes += end - start;
                        else if (ip->i_depth)
                                revokes += sdp->sd_inptrs;
                        ret = gfs2_trans_begin(sdp, jblocks_rqsted, revokes);
@@ -1226,7 +1219,11 @@ out_unlock:
                                            outside the rgrp we just processed,
                                            do it all over again. */
                if (current->journal_info) {
-                       struct buffer_head *dibh = mp->mp_bh[0];
+                       struct buffer_head *dibh;
+
+                       ret = gfs2_meta_inode_buffer(ip, &dibh);
+                       if (ret)
+                               goto out;
 
                        /* Every transaction boundary, we rewrite the dinode
                           to keep its di_blocks current in case of failure. */
@@ -1234,6 +1231,7 @@ out_unlock:
                                current_time(&ip->i_inode);
                        gfs2_trans_add_meta(ip->i_gl, dibh);
                        gfs2_dinode_out(ip, dibh->b_data);
+                       brelse(dibh);
                        up_write(&ip->i_rw_mutex);
                        gfs2_trans_end(sdp);
                }
@@ -1286,13 +1284,30 @@ enum dealloc_states {
        DEALLOC_DONE = 3,       /* process complete */
 };
 
-static bool mp_eq_to_hgt(struct metapath *mp, __u16 *nbof, unsigned int h)
+static bool mp_eq_to_hgt(struct metapath *mp, __u16 *list, unsigned int h)
 {
-       if (memcmp(mp->mp_list, nbof, h * sizeof(mp->mp_list[0])))
+       if (memcmp(mp->mp_list, list, h * sizeof(mp->mp_list[0])))
                return false;
        return true;
 }
 
+static inline void
+metapointer_range(struct metapath *mp, int height,
+                 __u16 *start_list, unsigned int start_aligned,
+                 __be64 **start, __be64 **end)
+{
+       struct buffer_head *bh = mp->mp_bh[height];
+       __be64 *first;
+
+       first = metaptr1(height, mp);
+       *start = first;
+       if (mp_eq_to_hgt(mp, start_list, height)) {
+               bool keep_start = height < start_aligned;
+               *start = first + start_list[height] + keep_start;
+       }
+       *end = (__be64 *)(bh->b_data + bh->b_size);
+}
+
 /**
  * trunc_dealloc - truncate a file down to a desired size
  * @ip: inode to truncate
@@ -1310,25 +1325,35 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
        struct metapath mp;
        struct buffer_head *dibh, *bh;
        struct gfs2_holder rd_gh;
-       u64 lblock;
-       __u16 nbof[GFS2_MAX_META_HEIGHT]; /* new beginning of truncation */
+       unsigned int bsize_shift = sdp->sd_sb.sb_bsize_shift;
+       u64 lblock = (newsize + (1 << bsize_shift) - 1) >> bsize_shift;
+       __u16 start_list[GFS2_MAX_META_HEIGHT]; /* new beginning of truncation */
+       unsigned int start_aligned;
        unsigned int strip_h = ip->i_height - 1;
        u32 btotal = 0;
        int ret, state;
        int mp_h; /* metapath buffers are read in to this height */
-       sector_t last_ra = 0;
        u64 prev_bnr = 0;
-       bool preserve1; /* need to preserve the first meta pointer? */
-
-       if (!newsize)
-               lblock = 0;
-       else
-               lblock = (newsize - 1) >> sdp->sd_sb.sb_bsize_shift;
+       __be64 *start, *end;
 
        memset(&mp, 0, sizeof(mp));
        find_metapath(sdp, lblock, &mp, ip->i_height);
 
-       memcpy(&nbof, &mp.mp_list, sizeof(nbof));
+       memcpy(start_list, mp.mp_list, sizeof(start_list));
+
+       /*
+        * Set start_aligned to the metadata height up to which the truncate
+        * point is aligned to the metadata tree (i.e., the truncate point is a
+        * multiple of the granularity at the height above).  This determines
+        * at which heights an additional meta pointer needs to be preserved:
+        * an additional meta pointer is needed at a given height if
+        * height < start_aligned.
+        */
+       for (mp_h = ip->i_height - 1; mp_h > 0; mp_h--) {
+               if (start_list[mp_h])
+                       break;
+       }
+       start_aligned = mp_h;
 
        ret = gfs2_meta_inode_buffer(ip, &dibh);
        if (ret)
@@ -1336,7 +1361,17 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
 
        mp.mp_bh[0] = dibh;
        ret = lookup_metapath(ip, &mp);
-       if (ret == ip->i_height)
+       if (ret)
+               goto out_metapath;
+
+       /* issue read-ahead on metadata */
+       for (mp_h = 0; mp_h < mp.mp_aheight - 1; mp_h++) {
+               metapointer_range(&mp, mp_h, start_list, start_aligned,
+                                 &start, &end);
+               gfs2_metapath_ra(ip->i_gl, start, end);
+       }
+
+       if (mp.mp_aheight == ip->i_height)
                state = DEALLOC_MP_FULL; /* We have a complete metapath */
        else
                state = DEALLOC_FILL_MP; /* deal with partial metapath */
@@ -1357,20 +1392,6 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
                /* Truncate a full metapath at the given strip height.
                 * Note that strip_h == mp_h in order to be in this state. */
                case DEALLOC_MP_FULL:
-                       if (mp_h > 0) { /* issue read-ahead on metadata */
-                               __be64 *top;
-
-                               bh = mp.mp_bh[mp_h - 1];
-                               if (bh->b_blocknr != last_ra) {
-                                       last_ra = bh->b_blocknr;
-                                       top = metaptr1(mp_h - 1, &mp);
-                                       gfs2_metapath_ra(ip->i_gl, bh, top);
-                               }
-                       }
-                       /* If we're truncating to a non-zero size and the mp is
-                          at the beginning of file for the strip height, we
-                          need to preserve the first metadata pointer. */
-                       preserve1 = (newsize && mp_eq_to_hgt(&mp, nbof, mp_h));
                        bh = mp.mp_bh[mp_h];
                        gfs2_assert_withdraw(sdp, bh);
                        if (gfs2_assert_withdraw(sdp,
@@ -1382,8 +1403,21 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
                                       prev_bnr, ip->i_height, strip_h, mp_h);
                        }
                        prev_bnr = bh->b_blocknr;
-                       ret = sweep_bh_for_rgrps(ip, &rd_gh, &mp, &btotal,
-                                                mp_h, preserve1);
+
+                       if (gfs2_metatype_check(sdp, bh,
+                                               (mp_h ? GFS2_METATYPE_IN :
+                                                       GFS2_METATYPE_DI))) {
+                               ret = -EIO;
+                               goto out;
+                       }
+
+                       metapointer_range(&mp, mp_h, start_list, start_aligned,
+                                         &start, &end);
+                       ret = sweep_bh_for_rgrps(ip, &rd_gh, mp.mp_bh[mp_h],
+                                                start, end,
+                                                mp_h != ip->i_height - 1,
+                                                &btotal);
+
                        /* If we hit an error or just swept dinode buffer,
                           just exit. */
                        if (ret || !mp_h) {
@@ -1407,7 +1441,7 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
                           stripping the previous level of metadata. */
                        if (mp_h == 0) {
                                strip_h--;
-                               memcpy(&mp.mp_list, &nbof, sizeof(nbof));
+                               memcpy(mp.mp_list, start_list, sizeof(start_list));
                                mp_h = strip_h;
                                state = DEALLOC_FILL_MP;
                                break;
@@ -1435,13 +1469,23 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
                        if (ret < 0)
                                goto out;
 
+                       /* issue read-ahead on metadata */
+                       if (mp.mp_aheight > 1) {
+                               for (; ret > 1; ret--) {
+                                       metapointer_range(&mp, mp.mp_aheight - ret,
+                                                         start_list, start_aligned,
+                                                         &start, &end);
+                                       gfs2_metapath_ra(ip->i_gl, start, end);
+                               }
+                       }
+
                        /* If buffers found for the entire strip height */
-                       if ((ret == ip->i_height) && (mp_h == strip_h)) {
+                       if (mp.mp_aheight - 1 == strip_h) {
                                state = DEALLOC_MP_FULL;
                                break;
                        }
-                       if (ret < ip->i_height) /* We have a partial height */
-                               mp_h = ret - 1;
+                       if (mp.mp_aheight < ip->i_height) /* We have a partial height */
+                               mp_h = mp.mp_aheight - 1;
 
                        /* If we find a non-null block pointer, crawl a bit
                           higher up in the metapath and try again, otherwise
@@ -1524,7 +1568,6 @@ out:
 /**
  * do_shrink - make a file smaller
  * @inode: the inode
- * @oldsize: the current inode size
  * @newsize: the size to make the file
  *
  * Called with an exclusive lock on @inode. The @size must
@@ -1533,12 +1576,12 @@ out:
  * Returns: errno
  */
 
-static int do_shrink(struct inode *inode, u64 oldsize, u64 newsize)
+static int do_shrink(struct inode *inode, u64 newsize)
 {
        struct gfs2_inode *ip = GFS2_I(inode);
        int error;
 
-       error = trunc_start(inode, oldsize, newsize);
+       error = trunc_start(inode, newsize);
        if (error < 0)
                return error;
        if (gfs2_is_stuffed(ip))
@@ -1553,10 +1596,9 @@ static int do_shrink(struct inode *inode, u64 oldsize, u64 newsize)
 
 void gfs2_trim_blocks(struct inode *inode)
 {
-       u64 size = inode->i_size;
        int ret;
 
-       ret = do_shrink(inode, size, size);
+       ret = do_shrink(inode, inode->i_size);
        WARN_ON(ret != 0);
 }
 
@@ -1650,7 +1692,6 @@ int gfs2_setattr_size(struct inode *inode, u64 newsize)
 {
        struct gfs2_inode *ip = GFS2_I(inode);
        int ret;
-       u64 oldsize;
 
        BUG_ON(!S_ISREG(inode->i_mode));
 
@@ -1664,13 +1705,12 @@ int gfs2_setattr_size(struct inode *inode, u64 newsize)
        if (ret)
                goto out;
 
-       oldsize = inode->i_size;
-       if (newsize >= oldsize) {
+       if (newsize >= inode->i_size) {
                ret = do_grow(inode, newsize);
                goto out;
        }
 
-       ret = do_shrink(inode, oldsize, newsize);
+       ret = do_shrink(inode, newsize);
 out:
        gfs2_rsqa_delete(ip, NULL);
        return ret;