On 22 December 2017 at 15:35, Andreas Gruenbacher <[email protected]> wrote:
> Add an upper bound to the range of blocks to deallocate blocks to
> function trunc_dealloc so that this function can be used for truncating
> a file as well as for punching a hole into a file.
>
> Signed-off-by: Andreas Gruenbacher <[email protected]>
> ---
>  fs/gfs2/bmap.c | 176 
> +++++++++++++++++++++++++++++++++++++++------------------
>  1 file changed, 120 insertions(+), 56 deletions(-)
>
> diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c
> index 984e27715a2e..da7e04b9973e 100644
> --- a/fs/gfs2/bmap.c
> +++ b/fs/gfs2/bmap.c
> @@ -461,13 +461,6 @@ enum alloc_state {
>         /* ALLOC_UNSTUFF = 3,   TBD and rather complicated */
>  };
>
> -static inline unsigned int hptrs(struct gfs2_sbd *sdp, const unsigned int 
> hgt)
> -{
> -       if (hgt)
> -               return sdp->sd_inptrs;
> -       return sdp->sd_diptrs;
> -}
> -
>  /**
>   * gfs2_bmap_alloc - Build a metadata tree of the requested height
>   * @inode: The GFS2 inode
> @@ -1240,6 +1233,13 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, 
> struct gfs2_holder *rd_gh,
>         return ret;
>  }
>
> +static bool mp_eq_to_hgt(struct metapath *mp, __u16 *list, unsigned int h)
> +{
> +       if (memcmp(mp->mp_list, list, h * sizeof(mp->mp_list[0])))
> +               return false;
> +       return true;
> +}
> +
>  /**
>   * find_nonnull_ptr - find a non-null pointer given a metapath and height
>   * assumes the metapath is valid (with buffers) out to height h
> @@ -1250,28 +1250,34 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, 
> struct gfs2_holder *rd_gh,
>   *          false if all remaining pointers are NULL in the buffer
>   */
>  static bool find_nonnull_ptr(struct gfs2_sbd *sdp, struct metapath *mp,
> -                            unsigned int h)
> +                            unsigned int h,
> +                            __u16 *end_list, unsigned int end_aligned)
>  {
> -       __be64 *ptr;
> -       unsigned int ptrs = hptrs(sdp, h) - 1;
> +       struct buffer_head *bh = mp->mp_bh[h];
> +       __be64 *first, *ptr, *end;
> +
> +       first = metaptr1(h, mp);
> +       ptr = first + mp->mp_list[h];
> +       end = (__be64 *)(bh->b_data + bh->b_size);
> +       if (end_list && mp_eq_to_hgt(mp, end_list, h)) {
> +               bool keep_end = h < end_aligned;
> +               end = first + end_list[h] + keep_end;
> +       }
>
> -       while (true) {
> -               ptr = metapointer(h, mp);
> +       while (ptr < end) {
>                 if (*ptr) { /* if we have a non-null pointer */
>                         /* Now zero the metapath after the current height. */
> +                       mp->mp_list[h] = ptr - first;
> +
>                         h++;
>                         if (h < GFS2_MAX_META_HEIGHT)
> -                               memset(&mp->mp_list[h], 0,
> -                                      (GFS2_MAX_META_HEIGHT - h) *
> -                                      sizeof(mp->mp_list[0]));
> +                               mp->mp_list[h + 1] = 0;

h is already incremented here, so this should be mp->mp_list[h] = 0;

>                         return true;
>                 }
>
> -               if (mp->mp_list[h] < ptrs)
> -                       mp->mp_list[h]++;
> -               else
> -                       return false; /* no more pointers in this buffer */
> +               ptr++;
>         }
> +       return false;
>  }
>
>  enum dealloc_states {
> @@ -1281,16 +1287,10 @@ enum dealloc_states {
>         DEALLOC_DONE = 3,       /* process complete */
>  };
>
> -static bool mp_eq_to_hgt(struct metapath *mp, __u16 *list, unsigned int h)
> -{
> -       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,
> +                 __u16 *end_list, unsigned int end_aligned,
>                   __be64 **start, __be64 **end)
>  {
>         struct buffer_head *bh = mp->mp_bh[height];
> @@ -1303,29 +1303,55 @@ metapointer_range(struct metapath *mp, int height,
>                 *start = first + start_list[height] + keep_start;
>         }
>         *end = (__be64 *)(bh->b_data + bh->b_size);
> +       if (end_list && mp_eq_to_hgt(mp, end_list, height)) {
> +               bool keep_end = height < end_aligned;
> +               *end = first + end_list[height] + keep_end;
> +       }
> +}
> +
> +static inline bool walk_done(struct gfs2_sbd *sdp,
> +                            struct metapath *mp, int height,
> +                            __u16 *end_list, unsigned int end_aligned)
> +{
> +       __u16 end;
> +
> +       if (end_list) {
> +               bool keep_end = height < end_aligned;
> +               if (!mp_eq_to_hgt(mp, end_list, height))
> +                       return false;
> +               end = end_list[height] + keep_end;
> +       } else
> +               end = (height > 0) ? sdp->sd_inptrs : sdp->sd_diptrs;
> +       return mp->mp_list[height] >= end;
>  }
>
>  /**
> - * trunc_dealloc - truncate a file down to a desired size
> + * punch_hole - deallocate blocks in a file
>   * @ip: inode to truncate
> - * @newsize: The desired size of the file
> + * @offset: the start of the hole
> + * @length: the size of the hole
> + *
> + * Punch a hole into a file or truncate a file at a given position.  This
> + * function operates in whole blocks (@offset and @length are rounded
> + * accordingly); partially filled blocks must be cleared otherwise.
>   *
> - * This function truncates a file to newsize. It works from the
> - * bottom up, and from the right to the left. In other words, it strips off
> - * the highest layer (data) before stripping any of the metadata. Doing it
> - * this way is best in case the operation is interrupted by power failure, 
> etc.
> - * The dinode is rewritten in every transaction to guarantee integrity.
> + * This function works from the bottom up, and from the right to the left. In
> + * other words, it strips off the highest layer (data) before stripping any 
> of
> + * the metadata. Doing it this way is best in case the operation is 
> interrupted
> + * by power failure, etc.  The dinode is rewritten in every transaction to
> + * guarantee integrity.
>   */
> -static int trunc_dealloc(struct gfs2_inode *ip, u64 newsize)
> +static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length)
>  {
>         struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode);
> -       struct metapath mp;
> +       struct metapath mp = {};
>         struct buffer_head *dibh, *bh;
>         struct gfs2_holder rd_gh;
>         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;
> +       u64 lblock = (offset + (1 << bsize_shift) - 1) >> bsize_shift;
> +       __u16 start_list[GFS2_MAX_META_HEIGHT];
> +       __u16 __end_list[GFS2_MAX_META_HEIGHT], *end_list = NULL;
> +       unsigned int start_aligned, end_aligned;
>         unsigned int strip_h = ip->i_height - 1;
>         u32 btotal = 0;
>         int ret, state;
> @@ -1333,19 +1359,49 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 
> newsize)
>         u64 prev_bnr = 0;
>         __be64 *start, *end;
>
> -       memset(&mp, 0, sizeof(mp));
> -       find_metapath(sdp, lblock, &mp, ip->i_height);
> +       /*
> +        * The start position of the hole is defined by lblock, start_list, 
> and
> +        * start_aligned.  The end position of the hole is defined by lend,
> +        * end_list, and end_aligned.
> +        *
> +        * start_aligned and end_aligned define down to which height the start
> +        * and end positions are aligned to the metadata tree (i.e., the
> +        * position is a multiple of the metadata granularity at the height
> +        * above).  This determines at which heights additional meta pointers
> +        * needs to be preserved for the remaining data.
> +        */
> +
> +       if (length) {
> +               u64 maxsize = sdp->sd_heightsize[ip->i_height];
> +               u64 end_offset = offset + length;
> +               u64 lend;
> +
> +               /*
> +                * Clip the end at the maximum file size for the given height:
> +                * that's how far the metadata goes; files bigger than that
> +                * will have additional layers of indirection.
> +                */
> +               if (end_offset > maxsize)
> +                       end_offset = maxsize;
> +               lend = end_offset >> bsize_shift;
> +
> +               if (lblock >= lend)
> +                       return 0;
>
> +               find_metapath(sdp, lend, &mp, ip->i_height);
> +               end_list = __end_list;
> +               memcpy(end_list, mp.mp_list, sizeof(mp.mp_list));
> +
> +               for (mp_h = ip->i_height - 1; mp_h > 0; mp_h--) {
> +                       if (end_list[mp_h])
> +                               break;
> +               }
> +               end_aligned = mp_h;
> +       }
> +
> +       find_metapath(sdp, lblock, &mp, ip->i_height);
>         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;
> @@ -1364,7 +1420,7 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 
> newsize)
>         /* 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);
> +                                 end_list, end_aligned, &start, &end);
>                 gfs2_metapath_ra(ip->i_gl, start, end);
>         }
>
> @@ -1408,7 +1464,14 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 
> newsize)
>                                 goto out;
>                         }
>
> +                       /*
> +                        * Below, passing end_aligned as 0 gives us the
> +                        * metapointer range excluding the end point: the end
> +                        * point is the first metapath we must not deallocate!
> +                        */
> +
>                         metapointer_range(&mp, mp_h, start_list, 
> start_aligned,
> +                                         end_list, 0 /* end_aligned */,
>                                           &start, &end);
>                         ret = sweep_bh_for_rgrps(ip, &rd_gh, mp.mp_bh[mp_h],
>                                                  start, end,
> @@ -1445,13 +1508,13 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 
> newsize)
>                         }
>                         mp.mp_list[mp_h] = 0;
>                         mp_h--; /* search one metadata height down */
> -                       if (mp.mp_list[mp_h] >= hptrs(sdp, mp_h) - 1)
> -                               break; /* loop around in the same state */
>                         mp.mp_list[mp_h]++;
> +                       if (walk_done(sdp, &mp, mp_h, end_list, end_aligned))
> +                               break;
>                         /* Here we've found a part of the metapath that is not
>                          * allocated. We need to search at that height for the
>                          * next non-null pointer. */
> -                       if (find_nonnull_ptr(sdp, &mp, mp_h)) {
> +                       if (find_nonnull_ptr(sdp, &mp, mp_h, end_list, 
> end_aligned)) {
>                                 state = DEALLOC_FILL_MP;
>                                 mp_h++;
>                         }
> @@ -1471,6 +1534,7 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 
> newsize)
>                                 for (; ret > 1; ret--) {
>                                         metapointer_range(&mp, mp.mp_aheight 
> - ret,
>                                                           start_list, 
> start_aligned,
> +                                                         end_list, 
> end_aligned,
>                                                           &start, &end);
>                                         gfs2_metapath_ra(ip->i_gl, start, 
> end);
>                                 }
> @@ -1487,7 +1551,7 @@ static int trunc_dealloc(struct gfs2_inode *ip, u64 
> newsize)
>                         /* If we find a non-null block pointer, crawl a bit
>                            higher up in the metapath and try again, otherwise
>                            we need to look lower for a new starting point. */
> -                       if (find_nonnull_ptr(sdp, &mp, mp_h))
> +                       if (find_nonnull_ptr(sdp, &mp, mp_h, end_list, 
> end_aligned))
>                                 mp_h++;
>                         else
>                                 state = DEALLOC_MP_LOWER;
> @@ -1584,7 +1648,7 @@ static int do_shrink(struct inode *inode, u64 newsize)
>         if (gfs2_is_stuffed(ip))
>                 return 0;
>
> -       error = trunc_dealloc(ip, newsize);
> +       error = punch_hole(ip, newsize, 0);
>         if (error == 0)
>                 error = trunc_end(ip);
>
> @@ -1716,7 +1780,7 @@ int gfs2_setattr_size(struct inode *inode, u64 newsize)
>  int gfs2_truncatei_resume(struct gfs2_inode *ip)
>  {
>         int error;
> -       error = trunc_dealloc(ip, i_size_read(&ip->i_inode));
> +       error = punch_hole(ip, i_size_read(&ip->i_inode), 0);
>         if (!error)
>                 error = trunc_end(ip);
>         return error;
> @@ -1724,7 +1788,7 @@ int gfs2_truncatei_resume(struct gfs2_inode *ip)
>
>  int gfs2_file_dealloc(struct gfs2_inode *ip)
>  {
> -       return trunc_dealloc(ip, 0);
> +       return punch_hole(ip, 0, 0);
>  }
>
>  /**
> --
> 2.14.3
>

Andreas

Reply via email to