xfs: create a separate cow extent size hint for the allocator
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 3 Oct 2016 16:11:43 +0000 (09:11 -0700)
committerDarrick J. Wong <darrick.wong@oracle.com>
Wed, 5 Oct 2016 23:26:26 +0000 (16:26 -0700)
Create a per-inode extent size allocator hint for copy-on-write.  This
hint is separate from the existing extent size hint so that CoW can
take advantage of the fragmentation-reducing properties of extent size
hints without disabling delalloc for regular writes.

The extent size hint that's fed to the allocator during a copy on
write operation is the greater of the cowextsize and regular extsize
hint.

During reflink, if we're sharing the entire source file to the entire
destination file and the destination file doesn't already have a
cowextsize hint, propagate the source file's cowextsize hint to the
destination file.

Furthermore, zero the bulkstat buffer prior to setting the fields
so that we don't copy kernel memory contents into userspace.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
15 files changed:
fs/xfs/libxfs/xfs_bmap.c
fs/xfs/libxfs/xfs_format.h
fs/xfs/libxfs/xfs_fs.h
fs/xfs/libxfs/xfs_inode_buf.c
fs/xfs/libxfs/xfs_inode_buf.h
fs/xfs/libxfs/xfs_log_format.h
fs/xfs/xfs_bmap_util.c
fs/xfs/xfs_inode.c
fs/xfs/xfs_inode.h
fs/xfs/xfs_inode_item.c
fs/xfs/xfs_ioctl.c
fs/xfs/xfs_iomap.c
fs/xfs/xfs_iomap.h
fs/xfs/xfs_itable.c
fs/xfs/xfs_reflink.c

index e4bf2c406e3b46897cb03093ccf4cce22ccd2474..9702f4c112bf42c82745890c845a4d95be315172 100644 (file)
@@ -3666,7 +3666,9 @@ xfs_bmap_btalloc(
        else if (mp->m_dalign)
                stripe_align = mp->m_dalign;
 
-       if (xfs_alloc_is_userdata(ap->datatype))
+       if (ap->flags & XFS_BMAPI_COWFORK)
+               align = xfs_get_cowextsz_hint(ap->ip);
+       else if (xfs_alloc_is_userdata(ap->datatype))
                align = xfs_get_extsz_hint(ap->ip);
        if (unlikely(align)) {
                error = xfs_bmap_extsize_align(mp, &ap->got, &ap->prev,
@@ -4192,7 +4194,10 @@ xfs_bmapi_reserve_delalloc(
                alen = XFS_FILBLKS_MIN(alen, got->br_startoff - aoff);
 
        /* Figure out the extent size, adjust alen */
-       extsz = xfs_get_extsz_hint(ip);
+       if (whichfork == XFS_COW_FORK)
+               extsz = xfs_get_cowextsz_hint(ip);
+       else
+               extsz = xfs_get_extsz_hint(ip);
        if (extsz) {
                error = xfs_bmap_extsize_align(mp, got, prev, extsz, rt, eof,
                                               1, 0, &aoff, &alen);
index 777fa974a3190daba0c9d0fa7f96cf4d23c2ef9c..5e04ea2aa902c5f061f5bf4289c4b1cd701d67be 100644 (file)
@@ -901,7 +901,8 @@ typedef struct xfs_dinode {
        __be64          di_changecount; /* number of attribute changes */
        __be64          di_lsn;         /* flush sequence */
        __be64          di_flags2;      /* more random flags */
-       __u8            di_pad2[16];    /* more padding for future expansion */
+       __be32          di_cowextsize;  /* basic cow extent size for file */
+       __u8            di_pad2[12];    /* more padding for future expansion */
 
        /* fields only written to during inode creation */
        xfs_timestamp_t di_crtime;      /* time created */
index 3d1efe5bf37bc5b9ebc4012dd0d8330a0233f017..b72dc821d78b97ba5f93a21122eab9946e2e947a 100644 (file)
@@ -278,7 +278,8 @@ typedef struct xfs_bstat {
 #define        bs_projid       bs_projid_lo    /* (previously just bs_projid)  */
        __u16           bs_forkoff;     /* inode fork offset in bytes   */
        __u16           bs_projid_hi;   /* higher part of project id    */
-       unsigned char   bs_pad[10];     /* pad space, unused            */
+       unsigned char   bs_pad[6];      /* pad space, unused            */
+       __u32           bs_cowextsize;  /* cow extent size              */
        __u32           bs_dmevmask;    /* DMIG event mask              */
        __u16           bs_dmstate;     /* DMIG state info              */
        __u16           bs_aextents;    /* attribute number of extents  */
index 4b9769e23c834278eabe70ea429dacabd2d934f2..a3e803823a19abe3716ad8f1317c58ff3b3a431e 100644 (file)
@@ -256,6 +256,7 @@ xfs_inode_from_disk(
                to->di_crtime.t_sec = be32_to_cpu(from->di_crtime.t_sec);
                to->di_crtime.t_nsec = be32_to_cpu(from->di_crtime.t_nsec);
                to->di_flags2 = be64_to_cpu(from->di_flags2);
+               to->di_cowextsize = be32_to_cpu(from->di_cowextsize);
        }
 }
 
@@ -305,7 +306,7 @@ xfs_inode_to_disk(
                to->di_crtime.t_sec = cpu_to_be32(from->di_crtime.t_sec);
                to->di_crtime.t_nsec = cpu_to_be32(from->di_crtime.t_nsec);
                to->di_flags2 = cpu_to_be64(from->di_flags2);
-
+               to->di_cowextsize = cpu_to_be32(from->di_cowextsize);
                to->di_ino = cpu_to_be64(ip->i_ino);
                to->di_lsn = cpu_to_be64(lsn);
                memset(to->di_pad2, 0, sizeof(to->di_pad2));
@@ -357,6 +358,7 @@ xfs_log_dinode_to_disk(
                to->di_crtime.t_sec = cpu_to_be32(from->di_crtime.t_sec);
                to->di_crtime.t_nsec = cpu_to_be32(from->di_crtime.t_nsec);
                to->di_flags2 = cpu_to_be64(from->di_flags2);
+               to->di_cowextsize = cpu_to_be32(from->di_cowextsize);
                to->di_ino = cpu_to_be64(from->di_ino);
                to->di_lsn = cpu_to_be64(from->di_lsn);
                memcpy(to->di_pad2, from->di_pad2, sizeof(to->di_pad2));
index 7c4dd321b2152915c2d9075222b4d757a08539cf..62d9d4681c8c28a1294b575903f0720693bc6666 100644 (file)
@@ -47,6 +47,7 @@ struct xfs_icdinode {
        __uint16_t      di_flags;       /* random flags, XFS_DIFLAG_... */
 
        __uint64_t      di_flags2;      /* more random flags */
+       __uint32_t      di_cowextsize;  /* basic cow extent size for file */
 
        xfs_ictimestamp_t di_crtime;    /* time created */
 };
index 5dd7c2e8f439a535e251557b3647d6134044b316..364ce6fcc2134eb4eaa66d4ae924ba0067a438be 100644 (file)
@@ -423,7 +423,8 @@ struct xfs_log_dinode {
        __uint64_t      di_changecount; /* number of attribute changes */
        xfs_lsn_t       di_lsn;         /* flush sequence */
        __uint64_t      di_flags2;      /* more random flags */
-       __uint8_t       di_pad2[16];    /* more padding for future expansion */
+       __uint32_t      di_cowextsize;  /* basic cow extent size for file */
+       __uint8_t       di_pad2[12];    /* more padding for future expansion */
 
        /* fields only written to during inode creation */
        xfs_ictimestamp_t di_crtime;    /* time created */
index e2f46e645612d8d3a1d2e132b98d89b24d15ad6b..4a807f05e460f283349d4495c16ea2651f7076dd 100644 (file)
@@ -582,8 +582,13 @@ xfs_getbmap(
                if (ip->i_cformat != XFS_DINODE_FMT_EXTENTS)
                        return -EINVAL;
 
-               prealloced = 0;
-               fixlen = XFS_ISIZE(ip);
+               if (xfs_get_cowextsz_hint(ip)) {
+                       prealloced = 1;
+                       fixlen = mp->m_super->s_maxbytes;
+               } else {
+                       prealloced = 0;
+                       fixlen = XFS_ISIZE(ip);
+               }
                break;
        default:
                if (ip->i_d.di_format != XFS_DINODE_FMT_EXTENTS &&
index f072f48b3c04aa8a644dc212f59a7605478d617d..09640bdd3e4483389c755a4e7caeff606c9fbd5b 100644 (file)
@@ -77,6 +77,27 @@ xfs_get_extsz_hint(
        return 0;
 }
 
+/*
+ * Helper function to extract CoW extent size hint from inode.
+ * Between the extent size hint and the CoW extent size hint, we
+ * return the greater of the two.
+ */
+xfs_extlen_t
+xfs_get_cowextsz_hint(
+       struct xfs_inode        *ip)
+{
+       xfs_extlen_t            a, b;
+
+       a = 0;
+       if (ip->i_d.di_flags2 & XFS_DIFLAG2_COWEXTSIZE)
+               a = ip->i_d.di_cowextsize;
+       b = xfs_get_extsz_hint(ip);
+
+       if (a > b)
+               return a;
+       return b;
+}
+
 /*
  * These two are wrapper routines around the xfs_ilock() routine used to
  * centralize some grungy code.  They are used in places that wish to lock the
@@ -652,6 +673,8 @@ _xfs_dic2xflags(
        if (di_flags2 & XFS_DIFLAG2_ANY) {
                if (di_flags2 & XFS_DIFLAG2_DAX)
                        flags |= FS_XFLAG_DAX;
+               if (di_flags2 & XFS_DIFLAG2_COWEXTSIZE)
+                       flags |= FS_XFLAG_COWEXTSIZE;
        }
 
        if (has_attr)
@@ -835,6 +858,7 @@ xfs_ialloc(
        if (ip->i_d.di_version == 3) {
                inode->i_version = 1;
                ip->i_d.di_flags2 = 0;
+               ip->i_d.di_cowextsize = 0;
                ip->i_d.di_crtime.t_sec = (__int32_t)tv.tv_sec;
                ip->i_d.di_crtime.t_nsec = (__int32_t)tv.tv_nsec;
        }
@@ -897,6 +921,15 @@ xfs_ialloc(
                        ip->i_d.di_flags |= di_flags;
                        ip->i_d.di_flags2 |= di_flags2;
                }
+               if (pip &&
+                   (pip->i_d.di_flags2 & XFS_DIFLAG2_ANY) &&
+                   pip->i_d.di_version == 3 &&
+                   ip->i_d.di_version == 3) {
+                       if (pip->i_d.di_flags2 & XFS_DIFLAG2_COWEXTSIZE) {
+                               ip->i_d.di_flags2 |= XFS_DIFLAG2_COWEXTSIZE;
+                               ip->i_d.di_cowextsize = pip->i_d.di_cowextsize;
+                       }
+               }
                /* FALLTHROUGH */
        case S_IFLNK:
                ip->i_d.di_format = XFS_DINODE_FMT_EXTENTS;
index ff6fc03c0092ded965cc6f63e4b88f2ddd1b40ab..6d63dc0311278788511a8c3066b98cea1be82147 100644 (file)
@@ -426,6 +426,7 @@ int         xfs_iflush(struct xfs_inode *, struct xfs_buf **);
 void           xfs_lock_two_inodes(xfs_inode_t *, xfs_inode_t *, uint);
 
 xfs_extlen_t   xfs_get_extsz_hint(struct xfs_inode *ip);
+xfs_extlen_t   xfs_get_cowextsz_hint(struct xfs_inode *ip);
 
 int            xfs_dir_ialloc(struct xfs_trans **, struct xfs_inode *, umode_t,
                               xfs_nlink_t, xfs_dev_t, prid_t, int,
index 892c2aced20788cb9875dc3a9cddee78dc0b1b8b..9610e9c0095277c7b53feb9ad5e230d6b156327c 100644 (file)
@@ -368,7 +368,7 @@ xfs_inode_to_log_dinode(
                to->di_crtime.t_sec = from->di_crtime.t_sec;
                to->di_crtime.t_nsec = from->di_crtime.t_nsec;
                to->di_flags2 = from->di_flags2;
-
+               to->di_cowextsize = from->di_cowextsize;
                to->di_ino = ip->i_ino;
                to->di_lsn = lsn;
                memset(to->di_pad2, 0, sizeof(to->di_pad2));
index 96a70fd1f5d6735b514f008bd165cf032bf7fd86..1388a1275dc8332590c8a073579ea9d348167435 100644 (file)
@@ -903,6 +903,8 @@ xfs_ioc_fsgetxattr(
        xfs_ilock(ip, XFS_ILOCK_SHARED);
        fa.fsx_xflags = xfs_ip2xflags(ip);
        fa.fsx_extsize = ip->i_d.di_extsize << ip->i_mount->m_sb.sb_blocklog;
+       fa.fsx_cowextsize = ip->i_d.di_cowextsize <<
+                       ip->i_mount->m_sb.sb_blocklog;
        fa.fsx_projid = xfs_get_projid(ip);
 
        if (attr) {
@@ -973,12 +975,13 @@ xfs_set_diflags(
        if (ip->i_d.di_version < 3)
                return;
 
-       di_flags2 = 0;
+       di_flags2 = (ip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK);
        if (xflags & FS_XFLAG_DAX)
                di_flags2 |= XFS_DIFLAG2_DAX;
+       if (xflags & FS_XFLAG_COWEXTSIZE)
+               di_flags2 |= XFS_DIFLAG2_COWEXTSIZE;
 
        ip->i_d.di_flags2 = di_flags2;
-
 }
 
 STATIC void
@@ -1219,6 +1222,56 @@ xfs_ioctl_setattr_check_extsize(
        return 0;
 }
 
+/*
+ * CoW extent size hint validation rules are:
+ *
+ * 1. CoW extent size hint can only be set if reflink is enabled on the fs.
+ *    The inode does not have to have any shared blocks, but it must be a v3.
+ * 2. FS_XFLAG_COWEXTSIZE is only valid for directories and regular files;
+ *    for a directory, the hint is propagated to new files.
+ * 3. Can be changed on files & directories at any time.
+ * 4. CoW extsize hint of 0 turns off hints, clears inode flags.
+ * 5. Extent size must be a multiple of the appropriate block size.
+ * 6. The extent size hint must be limited to half the AG size to avoid
+ *    alignment extending the extent beyond the limits of the AG.
+ */
+static int
+xfs_ioctl_setattr_check_cowextsize(
+       struct xfs_inode        *ip,
+       struct fsxattr          *fa)
+{
+       struct xfs_mount        *mp = ip->i_mount;
+
+       if (!(fa->fsx_xflags & FS_XFLAG_COWEXTSIZE))
+               return 0;
+
+       if (!xfs_sb_version_hasreflink(&ip->i_mount->m_sb) ||
+           ip->i_d.di_version != 3)
+               return -EINVAL;
+
+       if (!S_ISREG(VFS_I(ip)->i_mode) && !S_ISDIR(VFS_I(ip)->i_mode))
+               return -EINVAL;
+
+       if (fa->fsx_cowextsize != 0) {
+               xfs_extlen_t    size;
+               xfs_fsblock_t   cowextsize_fsb;
+
+               cowextsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_cowextsize);
+               if (cowextsize_fsb > MAXEXTLEN)
+                       return -EINVAL;
+
+               size = mp->m_sb.sb_blocksize;
+               if (cowextsize_fsb > mp->m_sb.sb_agblocks / 2)
+                       return -EINVAL;
+
+               if (fa->fsx_cowextsize % size)
+                       return -EINVAL;
+       } else
+               fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
+
+       return 0;
+}
+
 static int
 xfs_ioctl_setattr_check_projid(
        struct xfs_inode        *ip,
@@ -1311,6 +1364,10 @@ xfs_ioctl_setattr(
        if (code)
                goto error_trans_cancel;
 
+       code = xfs_ioctl_setattr_check_cowextsize(ip, fa);
+       if (code)
+               goto error_trans_cancel;
+
        code = xfs_ioctl_setattr_xflags(tp, ip, fa);
        if (code)
                goto error_trans_cancel;
@@ -1346,6 +1403,12 @@ xfs_ioctl_setattr(
                ip->i_d.di_extsize = fa->fsx_extsize >> mp->m_sb.sb_blocklog;
        else
                ip->i_d.di_extsize = 0;
+       if (ip->i_d.di_version == 3 &&
+           (ip->i_d.di_flags2 & XFS_DIFLAG2_COWEXTSIZE))
+               ip->i_d.di_cowextsize = fa->fsx_cowextsize >>
+                               mp->m_sb.sb_blocklog;
+       else
+               ip->i_d.di_cowextsize = 0;
 
        code = xfs_trans_commit(tp);
 
index 765849ed9b7017e861e8026c90ff30713bd83097..d907eb9f8ef32a079f845c4aa4731bb3413fcb25 100644 (file)
@@ -71,7 +71,7 @@ xfs_bmbt_to_iomap(
        iomap->bdev = xfs_find_bdev_for_inode(VFS_I(ip));
 }
 
-static xfs_extlen_t
+xfs_extlen_t
 xfs_eof_alignment(
        struct xfs_inode        *ip,
        xfs_extlen_t            extsize)
index a16b9567e76b70664f62bfdbee184c0a2605bee7..6d45cf01fcffb01e123cb75d3a2707318c4cac31 100644 (file)
@@ -31,6 +31,7 @@ int xfs_iomap_write_unwritten(struct xfs_inode *, xfs_off_t, xfs_off_t);
 
 void xfs_bmbt_to_iomap(struct xfs_inode *, struct iomap *,
                struct xfs_bmbt_irec *);
+xfs_extlen_t xfs_eof_alignment(struct xfs_inode *ip, xfs_extlen_t extsize);
 
 extern struct iomap_ops xfs_iomap_ops;
 extern struct iomap_ops xfs_xattr_iomap_ops;
index ce73eb34620dbbf06570650a582163a98a0d8f92..66e881790c17109496e21bd5e2d7d21f5ecc7fe5 100644 (file)
@@ -66,7 +66,7 @@ xfs_bulkstat_one_int(
        if (!buffer || xfs_internal_inum(mp, ino))
                return -EINVAL;
 
-       buf = kmem_alloc(sizeof(*buf), KM_SLEEP | KM_MAYFAIL);
+       buf = kmem_zalloc(sizeof(*buf), KM_SLEEP | KM_MAYFAIL);
        if (!buf)
                return -ENOMEM;
 
@@ -111,6 +111,12 @@ xfs_bulkstat_one_int(
        buf->bs_aextents = dic->di_anextents;
        buf->bs_forkoff = XFS_IFORK_BOFF(ip);
 
+       if (dic->di_version == 3) {
+               if (dic->di_flags2 & XFS_DIFLAG2_COWEXTSIZE)
+                       buf->bs_cowextsize = dic->di_cowextsize <<
+                                       mp->m_sb.sb_blocklog;
+       }
+
        switch (dic->di_format) {
        case XFS_DINODE_FMT_DEV:
                buf->bs_rdev = ip->i_df.if_u2.if_rdev;
index d4707e627a740b4beecb61b1c5918a10701a01be..8e894118295fec9154adc520abe814d931c7b74a 100644 (file)
@@ -238,6 +238,7 @@ __xfs_reflink_reserve_cow(
        int                     nimaps, eof = 0, error = 0;
        bool                    shared = false, trimmed = false;
        xfs_extnum_t            idx;
+       xfs_extlen_t            align;
 
        /* Already reserved?  Skip the refcount btree access. */
        xfs_bmap_search_extents(ip, *offset_fsb, XFS_COW_FORK, &eof, &idx,
@@ -277,6 +278,10 @@ __xfs_reflink_reserve_cow(
        if (error)
                goto out_unlock;
 
+       align = xfs_eof_alignment(ip, xfs_get_cowextsz_hint(ip));
+       if (align)
+               end_fsb = roundup_64(end_fsb, align);
+
 retry:
        error = xfs_bmapi_reserve_delalloc(ip, XFS_COW_FORK, *offset_fsb,
                        end_fsb - *offset_fsb, &got,
@@ -927,18 +932,19 @@ out_error:
 }
 
 /*
- * Update destination inode size, if necessary.
+ * Update destination inode size & cowextsize hint, if necessary.
  */
 STATIC int
 xfs_reflink_update_dest(
        struct xfs_inode        *dest,
-       xfs_off_t               newlen)
+       xfs_off_t               newlen,
+       xfs_extlen_t            cowextsize)
 {
        struct xfs_mount        *mp = dest->i_mount;
        struct xfs_trans        *tp;
        int                     error;
 
-       if (newlen <= i_size_read(VFS_I(dest)))
+       if (newlen <= i_size_read(VFS_I(dest)) && cowextsize == 0)
                return 0;
 
        error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
@@ -948,9 +954,17 @@ xfs_reflink_update_dest(
        xfs_ilock(dest, XFS_ILOCK_EXCL);
        xfs_trans_ijoin(tp, dest, XFS_ILOCK_EXCL);
 
-       trace_xfs_reflink_update_inode_size(dest, newlen);
-       i_size_write(VFS_I(dest), newlen);
-       dest->i_d.di_size = newlen;
+       if (newlen > i_size_read(VFS_I(dest))) {
+               trace_xfs_reflink_update_inode_size(dest, newlen);
+               i_size_write(VFS_I(dest), newlen);
+               dest->i_d.di_size = newlen;
+       }
+
+       if (cowextsize) {
+               dest->i_d.di_cowextsize = cowextsize;
+               dest->i_d.di_flags2 |= XFS_DIFLAG2_COWEXTSIZE;
+       }
+
        xfs_trans_log_inode(tp, dest, XFS_ILOG_CORE);
 
        error = xfs_trans_commit(tp);
@@ -1270,6 +1284,7 @@ xfs_reflink_remap_range(
        xfs_fileoff_t           sfsbno, dfsbno;
        xfs_filblks_t           fsblen;
        int                     error;
+       xfs_extlen_t            cowextsize;
        bool                    is_same;
 
        if (!xfs_sb_version_hasreflink(&mp->m_sb))
@@ -1330,7 +1345,19 @@ xfs_reflink_remap_range(
        if (error)
                goto out_error;
 
-       error = xfs_reflink_update_dest(dest, destoff + len);
+       /*
+        * Carry the cowextsize hint from src to dest if we're sharing the
+        * entire source file to the entire destination file, the source file
+        * has a cowextsize hint, and the destination file does not.
+        */
+       cowextsize = 0;
+       if (srcoff == 0 && len == i_size_read(VFS_I(src)) &&
+           (src->i_d.di_flags2 & XFS_DIFLAG2_COWEXTSIZE) &&
+           destoff == 0 && len >= i_size_read(VFS_I(dest)) &&
+           !(dest->i_d.di_flags2 & XFS_DIFLAG2_COWEXTSIZE))
+               cowextsize = src->i_d.di_cowextsize;
+
+       error = xfs_reflink_update_dest(dest, destoff + len, cowextsize);
        if (error)
                goto out_error;