Merge branch 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetoot...
[cascardo/linux.git] / fs / xfs / libxfs / xfs_inode_fork.c
index bbcc8c7..5dd56d3 100644 (file)
@@ -121,6 +121,26 @@ xfs_iformat_fork(
                return -EFSCORRUPTED;
        }
 
+       if (unlikely(xfs_is_reflink_inode(ip) &&
+           (VFS_I(ip)->i_mode & S_IFMT) != S_IFREG)) {
+               xfs_warn(ip->i_mount,
+                       "corrupt dinode %llu, wrong file type for reflink.",
+                       ip->i_ino);
+               XFS_CORRUPTION_ERROR("xfs_iformat(reflink)",
+                                    XFS_ERRLEVEL_LOW, ip->i_mount, dip);
+               return -EFSCORRUPTED;
+       }
+
+       if (unlikely(xfs_is_reflink_inode(ip) &&
+           (ip->i_d.di_flags & XFS_DIFLAG_REALTIME))) {
+               xfs_warn(ip->i_mount,
+                       "corrupt dinode %llu, has reflink+realtime flag set.",
+                       ip->i_ino);
+               XFS_CORRUPTION_ERROR("xfs_iformat(reflink)",
+                                    XFS_ERRLEVEL_LOW, ip->i_mount, dip);
+               return -EFSCORRUPTED;
+       }
+
        switch (VFS_I(ip)->i_mode & S_IFMT) {
        case S_IFIFO:
        case S_IFCHR:
@@ -186,9 +206,14 @@ xfs_iformat_fork(
                XFS_ERROR_REPORT("xfs_iformat(7)", XFS_ERRLEVEL_LOW, ip->i_mount);
                return -EFSCORRUPTED;
        }
-       if (error) {
+       if (error)
                return error;
+
+       if (xfs_is_reflink_inode(ip)) {
+               ASSERT(ip->i_cowfp == NULL);
+               xfs_ifork_init_cow(ip);
        }
+
        if (!XFS_DFORK_Q(dip))
                return 0;
 
@@ -208,7 +233,8 @@ xfs_iformat_fork(
                        XFS_CORRUPTION_ERROR("xfs_iformat(8)",
                                             XFS_ERRLEVEL_LOW,
                                             ip->i_mount, dip);
-                       return -EFSCORRUPTED;
+                       error = -EFSCORRUPTED;
+                       break;
                }
 
                error = xfs_iformat_local(ip, dip, XFS_ATTR_FORK, size);
@@ -226,6 +252,9 @@ xfs_iformat_fork(
        if (error) {
                kmem_zone_free(xfs_ifork_zone, ip->i_afp);
                ip->i_afp = NULL;
+               if (ip->i_cowfp)
+                       kmem_zone_free(xfs_ifork_zone, ip->i_cowfp);
+               ip->i_cowfp = NULL;
                xfs_idestroy_fork(ip, XFS_DATA_FORK);
        }
        return error;
@@ -740,6 +769,9 @@ xfs_idestroy_fork(
        if (whichfork == XFS_ATTR_FORK) {
                kmem_zone_free(xfs_ifork_zone, ip->i_afp);
                ip->i_afp = NULL;
+       } else if (whichfork == XFS_COW_FORK) {
+               kmem_zone_free(xfs_ifork_zone, ip->i_cowfp);
+               ip->i_cowfp = NULL;
        }
 }
 
@@ -927,6 +959,19 @@ xfs_iext_get_ext(
        }
 }
 
+/* Convert bmap state flags to an inode fork. */
+struct xfs_ifork *
+xfs_iext_state_to_fork(
+       struct xfs_inode        *ip,
+       int                     state)
+{
+       if (state & BMAP_COWFORK)
+               return ip->i_cowfp;
+       else if (state & BMAP_ATTRFORK)
+               return ip->i_afp;
+       return &ip->i_df;
+}
+
 /*
  * Insert new item(s) into the extent records for incore inode
  * fork 'ifp'.  'count' new items are inserted at index 'idx'.
@@ -939,7 +984,7 @@ xfs_iext_insert(
        xfs_bmbt_irec_t *new,           /* items to insert */
        int             state)          /* type of extent conversion */
 {
-       xfs_ifork_t     *ifp = (state & BMAP_ATTRFORK) ? ip->i_afp : &ip->i_df;
+       xfs_ifork_t     *ifp = xfs_iext_state_to_fork(ip, state);
        xfs_extnum_t    i;              /* extent record index */
 
        trace_xfs_iext_insert(ip, idx, new, state, _RET_IP_);
@@ -1189,7 +1234,7 @@ xfs_iext_remove(
        int             ext_diff,       /* number of extents to remove */
        int             state)          /* type of extent conversion */
 {
-       xfs_ifork_t     *ifp = (state & BMAP_ATTRFORK) ? ip->i_afp : &ip->i_df;
+       xfs_ifork_t     *ifp = xfs_iext_state_to_fork(ip, state);
        xfs_extnum_t    nextents;       /* number of extents in file */
        int             new_size;       /* size of extents after removal */
 
@@ -1934,3 +1979,20 @@ xfs_iext_irec_update_extoffs(
                ifp->if_u1.if_ext_irec[i].er_extoff += ext_diff;
        }
 }
+
+/*
+ * Initialize an inode's copy-on-write fork.
+ */
+void
+xfs_ifork_init_cow(
+       struct xfs_inode        *ip)
+{
+       if (ip->i_cowfp)
+               return;
+
+       ip->i_cowfp = kmem_zone_zalloc(xfs_ifork_zone,
+                                      KM_SLEEP | KM_NOFS);
+       ip->i_cowfp->if_flags = XFS_IFEXTENTS;
+       ip->i_cformat = XFS_DINODE_FMT_EXTENTS;
+       ip->i_cnextents = 0;
+}