xfs: support removing extents from CoW fork
[cascardo/linux.git] / fs / xfs / libxfs / xfs_bmap.c
index dc24dd0..8758000 100644 (file)
@@ -141,7 +141,8 @@ xfs_bmbt_lookup_ge(
  */
 static inline bool xfs_bmap_needs_btree(struct xfs_inode *ip, int whichfork)
 {
-       return XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_EXTENTS &&
+       return whichfork != XFS_COW_FORK &&
+               XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_EXTENTS &&
                XFS_IFORK_NEXTENTS(ip, whichfork) >
                        XFS_IFORK_MAXEXT(ip, whichfork);
 }
@@ -151,7 +152,8 @@ static inline bool xfs_bmap_needs_btree(struct xfs_inode *ip, int whichfork)
  */
 static inline bool xfs_bmap_wants_extents(struct xfs_inode *ip, int whichfork)
 {
-       return XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_BTREE &&
+       return whichfork != XFS_COW_FORK &&
+               XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_BTREE &&
                XFS_IFORK_NEXTENTS(ip, whichfork) <=
                        XFS_IFORK_MAXEXT(ip, whichfork);
 }
@@ -641,6 +643,7 @@ xfs_bmap_btree_to_extents(
 
        mp = ip->i_mount;
        ifp = XFS_IFORK_PTR(ip, whichfork);
+       ASSERT(whichfork != XFS_COW_FORK);
        ASSERT(ifp->if_flags & XFS_IFEXTENTS);
        ASSERT(XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_BTREE);
        rblock = ifp->if_broot;
@@ -707,6 +710,7 @@ xfs_bmap_extents_to_btree(
        xfs_bmbt_ptr_t          *pp;            /* root block address pointer */
 
        mp = ip->i_mount;
+       ASSERT(whichfork != XFS_COW_FORK);
        ifp = XFS_IFORK_PTR(ip, whichfork);
        ASSERT(XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_EXTENTS);
 
@@ -838,6 +842,7 @@ xfs_bmap_local_to_extents_empty(
 {
        struct xfs_ifork        *ifp = XFS_IFORK_PTR(ip, whichfork);
 
+       ASSERT(whichfork != XFS_COW_FORK);
        ASSERT(XFS_IFORK_FORMAT(ip, whichfork) == XFS_DINODE_FMT_LOCAL);
        ASSERT(ifp->if_bytes == 0);
        ASSERT(XFS_IFORK_NEXTENTS(ip, whichfork) == 0);
@@ -1671,7 +1676,8 @@ xfs_bmap_one_block(
  */
 STATIC int                             /* error */
 xfs_bmap_add_extent_delay_real(
-       struct xfs_bmalloca     *bma)
+       struct xfs_bmalloca     *bma,
+       int                     whichfork)
 {
        struct xfs_bmbt_irec    *new = &bma->got;
        int                     diff;   /* temp value */
@@ -1689,11 +1695,14 @@ xfs_bmap_add_extent_delay_real(
        xfs_filblks_t           temp=0; /* value for da_new calculations */
        xfs_filblks_t           temp2=0;/* value for da_new calculations */
        int                     tmp_rval;       /* partial logging flags */
-       int                     whichfork = XFS_DATA_FORK;
        struct xfs_mount        *mp;
+       xfs_extnum_t            *nextents;
 
        mp = bma->ip->i_mount;
        ifp = XFS_IFORK_PTR(bma->ip, whichfork);
+       ASSERT(whichfork != XFS_ATTR_FORK);
+       nextents = (whichfork == XFS_COW_FORK ? &bma->ip->i_cnextents :
+                                               &bma->ip->i_d.di_nextents);
 
        ASSERT(bma->idx >= 0);
        ASSERT(bma->idx <= ifp->if_bytes / sizeof(struct xfs_bmbt_rec));
@@ -1707,6 +1716,9 @@ xfs_bmap_add_extent_delay_real(
 #define        RIGHT           r[1]
 #define        PREV            r[2]
 
+       if (whichfork == XFS_COW_FORK)
+               state |= BMAP_COWFORK;
+
        /*
         * Set up a bunch of variables to make the tests simpler.
         */
@@ -1793,7 +1805,7 @@ xfs_bmap_add_extent_delay_real(
                trace_xfs_bmap_post_update(bma->ip, bma->idx, state, _THIS_IP_);
 
                xfs_iext_remove(bma->ip, bma->idx + 1, 2, state);
-               bma->ip->i_d.di_nextents--;
+               (*nextents)--;
                if (bma->cur == NULL)
                        rval = XFS_ILOG_CORE | XFS_ILOG_DEXT;
                else {
@@ -1895,7 +1907,7 @@ xfs_bmap_add_extent_delay_real(
                xfs_bmbt_set_startblock(ep, new->br_startblock);
                trace_xfs_bmap_post_update(bma->ip, bma->idx, state, _THIS_IP_);
 
-               bma->ip->i_d.di_nextents++;
+               (*nextents)++;
                if (bma->cur == NULL)
                        rval = XFS_ILOG_CORE | XFS_ILOG_DEXT;
                else {
@@ -1965,7 +1977,7 @@ xfs_bmap_add_extent_delay_real(
                temp = PREV.br_blockcount - new->br_blockcount;
                xfs_bmbt_set_blockcount(ep, temp);
                xfs_iext_insert(bma->ip, bma->idx, 1, new, state);
-               bma->ip->i_d.di_nextents++;
+               (*nextents)++;
                if (bma->cur == NULL)
                        rval = XFS_ILOG_CORE | XFS_ILOG_DEXT;
                else {
@@ -2049,7 +2061,7 @@ xfs_bmap_add_extent_delay_real(
                trace_xfs_bmap_pre_update(bma->ip, bma->idx, state, _THIS_IP_);
                xfs_bmbt_set_blockcount(ep, temp);
                xfs_iext_insert(bma->ip, bma->idx + 1, 1, new, state);
-               bma->ip->i_d.di_nextents++;
+               (*nextents)++;
                if (bma->cur == NULL)
                        rval = XFS_ILOG_CORE | XFS_ILOG_DEXT;
                else {
@@ -2118,7 +2130,7 @@ xfs_bmap_add_extent_delay_real(
                RIGHT.br_blockcount = temp2;
                /* insert LEFT (r[0]) and RIGHT (r[1]) at the same time */
                xfs_iext_insert(bma->ip, bma->idx + 1, 2, &LEFT, state);
-               bma->ip->i_d.di_nextents++;
+               (*nextents)++;
                if (bma->cur == NULL)
                        rval = XFS_ILOG_CORE | XFS_ILOG_DEXT;
                else {
@@ -2216,7 +2228,8 @@ xfs_bmap_add_extent_delay_real(
 
        xfs_bmap_check_leaf_extents(bma->cur, bma->ip, whichfork);
 done:
-       bma->logflags |= rval;
+       if (whichfork != XFS_COW_FORK)
+               bma->logflags |= rval;
        return error;
 #undef LEFT
 #undef RIGHT
@@ -2760,6 +2773,7 @@ done:
 STATIC void
 xfs_bmap_add_extent_hole_delay(
        xfs_inode_t             *ip,    /* incore inode pointer */
+       int                     whichfork,
        xfs_extnum_t            *idx,   /* extent number to update/insert */
        xfs_bmbt_irec_t         *new)   /* new data to add to file extents */
 {
@@ -2771,8 +2785,10 @@ xfs_bmap_add_extent_hole_delay(
        int                     state;  /* state bits, accessed thru macros */
        xfs_filblks_t           temp=0; /* temp for indirect calculations */
 
-       ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
+       ifp = XFS_IFORK_PTR(ip, whichfork);
        state = 0;
+       if (whichfork == XFS_COW_FORK)
+               state |= BMAP_COWFORK;
        ASSERT(isnullstartblock(new->br_startblock));
 
        /*
@@ -2790,7 +2806,7 @@ xfs_bmap_add_extent_hole_delay(
         * Check and set flags if the current (right) segment exists.
         * If it doesn't exist, we're converting the hole at end-of-file.
         */
-       if (*idx < ip->i_df.if_bytes / (uint)sizeof(xfs_bmbt_rec_t)) {
+       if (*idx < ifp->if_bytes / (uint)sizeof(xfs_bmbt_rec_t)) {
                state |= BMAP_RIGHT_VALID;
                xfs_bmbt_get_all(xfs_iext_get_ext(ifp, *idx), &right);
 
@@ -3858,7 +3874,8 @@ xfs_bmap_btalloc(
                ASSERT(nullfb || fb_agno == args.agno ||
                       (ap->dfops->dop_low && fb_agno < args.agno));
                ap->length = args.len;
-               ap->ip->i_d.di_nblocks += args.len;
+               if (!(ap->flags & XFS_BMAPI_COWFORK))
+                       ap->ip->i_d.di_nblocks += args.len;
                xfs_trans_log_inode(ap->tp, ap->ip, XFS_ILOG_CORE);
                if (ap->wasdel)
                        ap->ip->i_delayed_blks -= args.len;
@@ -4154,6 +4171,7 @@ xfs_bmapi_read(
 int
 xfs_bmapi_reserve_delalloc(
        struct xfs_inode        *ip,
+       int                     whichfork,
        xfs_fileoff_t           aoff,
        xfs_filblks_t           len,
        struct xfs_bmbt_irec    *got,
@@ -4162,7 +4180,7 @@ xfs_bmapi_reserve_delalloc(
        int                     eof)
 {
        struct xfs_mount        *mp = ip->i_mount;
-       struct xfs_ifork        *ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
+       struct xfs_ifork        *ifp = XFS_IFORK_PTR(ip, whichfork);
        xfs_extlen_t            alen;
        xfs_extlen_t            indlen;
        char                    rt = XFS_IS_REALTIME_INODE(ip);
@@ -4221,7 +4239,7 @@ xfs_bmapi_reserve_delalloc(
        got->br_startblock = nullstartblock(indlen);
        got->br_blockcount = alen;
        got->br_state = XFS_EXT_NORM;
-       xfs_bmap_add_extent_hole_delay(ip, lastx, got);
+       xfs_bmap_add_extent_hole_delay(ip, whichfork, lastx, got);
 
        /*
         * Update our extent pointer, given that xfs_bmap_add_extent_hole_delay
@@ -4252,8 +4270,7 @@ xfs_bmapi_allocate(
        struct xfs_bmalloca     *bma)
 {
        struct xfs_mount        *mp = bma->ip->i_mount;
-       int                     whichfork = (bma->flags & XFS_BMAPI_ATTRFORK) ?
-                                               XFS_ATTR_FORK : XFS_DATA_FORK;
+       int                     whichfork = xfs_bmapi_whichfork(bma->flags);
        struct xfs_ifork        *ifp = XFS_IFORK_PTR(bma->ip, whichfork);
        int                     tmp_logflags = 0;
        int                     error;
@@ -4348,7 +4365,7 @@ xfs_bmapi_allocate(
                bma->got.br_state = XFS_EXT_UNWRITTEN;
 
        if (bma->wasdel)
-               error = xfs_bmap_add_extent_delay_real(bma);
+               error = xfs_bmap_add_extent_delay_real(bma, whichfork);
        else
                error = xfs_bmap_add_extent_hole_real(bma, whichfork);
 
@@ -4502,8 +4519,7 @@ xfs_bmapi_write(
        orig_mval = mval;
        orig_nmap = *nmap;
 #endif
-       whichfork = (flags & XFS_BMAPI_ATTRFORK) ?
-               XFS_ATTR_FORK : XFS_DATA_FORK;
+       whichfork = xfs_bmapi_whichfork(flags);
 
        ASSERT(*nmap >= 1);
        ASSERT(*nmap <= XFS_BMAP_MAX_NMAP);
@@ -4515,6 +4531,8 @@ xfs_bmapi_write(
        ASSERT(!(flags & XFS_BMAPI_REMAP) || whichfork == XFS_DATA_FORK);
        ASSERT(!(flags & XFS_BMAPI_PREALLOC) || !(flags & XFS_BMAPI_REMAP));
        ASSERT(!(flags & XFS_BMAPI_CONVERT) || !(flags & XFS_BMAPI_REMAP));
+       ASSERT(!(flags & XFS_BMAPI_PREALLOC) || whichfork != XFS_COW_FORK);
+       ASSERT(!(flags & XFS_BMAPI_CONVERT) || whichfork != XFS_COW_FORK);
 
        /* zeroing is for currently only for data extents, not metadata */
        ASSERT((flags & (XFS_BMAPI_METADATA | XFS_BMAPI_ZERO)) !=
@@ -4580,6 +4598,8 @@ xfs_bmapi_write(
                 */
                if (flags & XFS_BMAPI_REMAP)
                        ASSERT(inhole);
+               if (flags & XFS_BMAPI_COWFORK)
+                       ASSERT(!inhole);
 
                /*
                 * First, deal with the hole before the allocated space
@@ -4888,6 +4908,7 @@ xfs_bmap_del_extent(
                /*
                 * Matches the whole extent.  Delete the entry.
                 */
+               trace_xfs_bmap_pre_update(ip, *idx, state, _THIS_IP_);
                xfs_iext_remove(ip, *idx, 1,
                                whichfork == XFS_ATTR_FORK ? BMAP_ATTRFORK : 0);
                --*idx;
@@ -5104,6 +5125,175 @@ done:
        return error;
 }
 
+/* Remove an extent from the CoW fork.  Similar to xfs_bmap_del_extent. */
+int
+xfs_bunmapi_cow(
+       struct xfs_inode                *ip,
+       struct xfs_bmbt_irec            *del)
+{
+       xfs_filblks_t                   da_new;
+       xfs_filblks_t                   da_old;
+       xfs_fsblock_t                   del_endblock = 0;
+       xfs_fileoff_t                   del_endoff;
+       int                             delay;
+       struct xfs_bmbt_rec_host        *ep;
+       int                             error;
+       struct xfs_bmbt_irec            got;
+       xfs_fileoff_t                   got_endoff;
+       struct xfs_ifork                *ifp;
+       struct xfs_mount                *mp;
+       xfs_filblks_t                   nblks;
+       struct xfs_bmbt_irec            new;
+       /* REFERENCED */
+       uint                            qfield;
+       xfs_filblks_t                   temp;
+       xfs_filblks_t                   temp2;
+       int                             state = BMAP_COWFORK;
+       int                             eof;
+       xfs_extnum_t                    eidx;
+
+       mp = ip->i_mount;
+       XFS_STATS_INC(mp, xs_del_exlist);
+
+       ep = xfs_bmap_search_extents(ip, del->br_startoff, XFS_COW_FORK, &eof,
+                       &eidx, &got, &new);
+
+       ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); ifp = ifp;
+       ASSERT((eidx >= 0) && (eidx < ifp->if_bytes /
+               (uint)sizeof(xfs_bmbt_rec_t)));
+       ASSERT(del->br_blockcount > 0);
+       ASSERT(got.br_startoff <= del->br_startoff);
+       del_endoff = del->br_startoff + del->br_blockcount;
+       got_endoff = got.br_startoff + got.br_blockcount;
+       ASSERT(got_endoff >= del_endoff);
+       delay = isnullstartblock(got.br_startblock);
+       ASSERT(isnullstartblock(del->br_startblock) == delay);
+       qfield = 0;
+       error = 0;
+       /*
+        * If deleting a real allocation, must free up the disk space.
+        */
+       if (!delay) {
+               nblks = del->br_blockcount;
+               qfield = XFS_TRANS_DQ_BCOUNT;
+               /*
+                * Set up del_endblock and cur for later.
+                */
+               del_endblock = del->br_startblock + del->br_blockcount;
+               da_old = da_new = 0;
+       } else {
+               da_old = startblockval(got.br_startblock);
+               da_new = 0;
+               nblks = 0;
+       }
+       qfield = qfield;
+       nblks = nblks;
+
+       /*
+        * Set flag value to use in switch statement.
+        * Left-contig is 2, right-contig is 1.
+        */
+       switch (((got.br_startoff == del->br_startoff) << 1) |
+               (got_endoff == del_endoff)) {
+       case 3:
+               /*
+                * Matches the whole extent.  Delete the entry.
+                */
+               xfs_iext_remove(ip, eidx, 1, BMAP_COWFORK);
+               --eidx;
+               break;
+
+       case 2:
+               /*
+                * Deleting the first part of the extent.
+                */
+               trace_xfs_bmap_pre_update(ip, eidx, state, _THIS_IP_);
+               xfs_bmbt_set_startoff(ep, del_endoff);
+               temp = got.br_blockcount - del->br_blockcount;
+               xfs_bmbt_set_blockcount(ep, temp);
+               if (delay) {
+                       temp = XFS_FILBLKS_MIN(xfs_bmap_worst_indlen(ip, temp),
+                               da_old);
+                       xfs_bmbt_set_startblock(ep, nullstartblock((int)temp));
+                       trace_xfs_bmap_post_update(ip, eidx, state, _THIS_IP_);
+                       da_new = temp;
+                       break;
+               }
+               xfs_bmbt_set_startblock(ep, del_endblock);
+               trace_xfs_bmap_post_update(ip, eidx, state, _THIS_IP_);
+               break;
+
+       case 1:
+               /*
+                * Deleting the last part of the extent.
+                */
+               temp = got.br_blockcount - del->br_blockcount;
+               trace_xfs_bmap_pre_update(ip, eidx, state, _THIS_IP_);
+               xfs_bmbt_set_blockcount(ep, temp);
+               if (delay) {
+                       temp = XFS_FILBLKS_MIN(xfs_bmap_worst_indlen(ip, temp),
+                               da_old);
+                       xfs_bmbt_set_startblock(ep, nullstartblock((int)temp));
+                       trace_xfs_bmap_post_update(ip, eidx, state, _THIS_IP_);
+                       da_new = temp;
+                       break;
+               }
+               trace_xfs_bmap_post_update(ip, eidx, state, _THIS_IP_);
+               break;
+
+       case 0:
+               /*
+                * Deleting the middle of the extent.
+                */
+               temp = del->br_startoff - got.br_startoff;
+               trace_xfs_bmap_pre_update(ip, eidx, state, _THIS_IP_);
+               xfs_bmbt_set_blockcount(ep, temp);
+               new.br_startoff = del_endoff;
+               temp2 = got_endoff - del_endoff;
+               new.br_blockcount = temp2;
+               new.br_state = got.br_state;
+               if (!delay) {
+                       new.br_startblock = del_endblock;
+               } else {
+                       temp = xfs_bmap_worst_indlen(ip, temp);
+                       xfs_bmbt_set_startblock(ep, nullstartblock((int)temp));
+                       temp2 = xfs_bmap_worst_indlen(ip, temp2);
+                       new.br_startblock = nullstartblock((int)temp2);
+                       da_new = temp + temp2;
+                       while (da_new > da_old) {
+                               if (temp) {
+                                       temp--;
+                                       da_new--;
+                                       xfs_bmbt_set_startblock(ep,
+                                               nullstartblock((int)temp));
+                               }
+                               if (da_new == da_old)
+                                       break;
+                               if (temp2) {
+                                       temp2--;
+                                       da_new--;
+                                       new.br_startblock =
+                                               nullstartblock((int)temp2);
+                               }
+                       }
+               }
+               trace_xfs_bmap_post_update(ip, eidx, state, _THIS_IP_);
+               xfs_iext_insert(ip, eidx + 1, 1, &new, state);
+               ++eidx;
+               break;
+       }
+
+       /*
+        * Account for change in delayed indirect blocks.
+        * Nothing to do for disk quota accounting here.
+        */
+       ASSERT(da_old >= da_new);
+       if (da_old > da_new)
+               xfs_mod_fdblocks(mp, (int64_t)(da_old - da_new), false);
+
+       return error;
+}
+
 /*
  * Unmap (remove) blocks from a file.
  * If nexts is nonzero then the number of extents to remove is limited to