xfs: map an inode's offset to an exact physical block
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 3 Oct 2016 16:11:27 +0000 (09:11 -0700)
committerDarrick J. Wong <darrick.wong@oracle.com>
Tue, 4 Oct 2016 18:05:44 +0000 (11:05 -0700)
Teach the bmap routine to know how to map a range of file blocks to a
specific range of physical blocks, instead of simply allocating fresh
blocks.  This enables reflink to map a file to blocks that are already
in use.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/libxfs/xfs_bmap.c
fs/xfs/libxfs/xfs_bmap.h
fs/xfs/xfs_trace.h

index 907d7b8df7bbcaab073b1b50184988d5befc486f..1ec3087bbb975206ea73fc3999de6f51942b5f4e 100644 (file)
@@ -3876,6 +3876,63 @@ xfs_bmap_btalloc(
        return 0;
 }
 
+/*
+ * For a remap operation, just "allocate" an extent at the address that the
+ * caller passed in, and ensure that the AGFL is the right size.  The caller
+ * will then map the "allocated" extent into the file somewhere.
+ */
+STATIC int
+xfs_bmap_remap_alloc(
+       struct xfs_bmalloca     *ap)
+{
+       struct xfs_trans        *tp = ap->tp;
+       struct xfs_mount        *mp = tp->t_mountp;
+       xfs_agblock_t           bno;
+       struct xfs_alloc_arg    args;
+       int                     error;
+
+       /*
+        * validate that the block number is legal - the enables us to detect
+        * and handle a silent filesystem corruption rather than crashing.
+        */
+       memset(&args, 0, sizeof(struct xfs_alloc_arg));
+       args.tp = ap->tp;
+       args.mp = ap->tp->t_mountp;
+       bno = *ap->firstblock;
+       args.agno = XFS_FSB_TO_AGNO(mp, bno);
+       args.agbno = XFS_FSB_TO_AGBNO(mp, bno);
+       if (args.agno >= mp->m_sb.sb_agcount ||
+           args.agbno >= mp->m_sb.sb_agblocks)
+               return -EFSCORRUPTED;
+
+       /* "Allocate" the extent from the range we passed in. */
+       trace_xfs_bmap_remap_alloc(ap->ip, *ap->firstblock, ap->length);
+       ap->blkno = bno;
+       ap->ip->i_d.di_nblocks += ap->length;
+       xfs_trans_log_inode(ap->tp, ap->ip, XFS_ILOG_CORE);
+
+       /* Fix the freelist, like a real allocator does. */
+       args.datatype = ap->datatype;
+       args.pag = xfs_perag_get(args.mp, args.agno);
+       ASSERT(args.pag);
+
+       /*
+        * The freelist fixing code will decline the allocation if
+        * the size and shape of the free space doesn't allow for
+        * allocating the extent and updating all the metadata that
+        * happens during an allocation.  We're remapping, not
+        * allocating, so skip that check by pretending to be freeing.
+        */
+       error = xfs_alloc_fix_freelist(&args, XFS_ALLOC_FLAG_FREEING);
+       if (error)
+               goto error0;
+error0:
+       xfs_perag_put(args.pag);
+       if (error)
+               trace_xfs_bmap_remap_alloc_error(ap->ip, error, _RET_IP_);
+       return error;
+}
+
 /*
  * xfs_bmap_alloc is called by xfs_bmapi to allocate an extent for a file.
  * It figures out where to ask the underlying allocator to put the new extent.
@@ -3884,6 +3941,8 @@ STATIC int
 xfs_bmap_alloc(
        struct xfs_bmalloca     *ap)    /* bmap alloc argument struct */
 {
+       if (ap->flags & XFS_BMAPI_REMAP)
+               return xfs_bmap_remap_alloc(ap);
        if (XFS_IS_REALTIME_INODE(ap->ip) &&
            xfs_alloc_is_userdata(ap->datatype))
                return xfs_bmap_rtalloc(ap);
@@ -4442,6 +4501,9 @@ xfs_bmapi_write(
        ASSERT(len > 0);
        ASSERT(XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL);
        ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
+       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));
 
        /* zeroing is for currently only for data extents, not metadata */
        ASSERT((flags & (XFS_BMAPI_METADATA | XFS_BMAPI_ZERO)) !=
@@ -4502,6 +4564,12 @@ xfs_bmapi_write(
                inhole = eof || bma.got.br_startoff > bno;
                wasdelay = !inhole && isnullstartblock(bma.got.br_startblock);
 
+               /*
+                * Make sure we only reflink into a hole.
+                */
+               if (flags & XFS_BMAPI_REMAP)
+                       ASSERT(inhole);
+
                /*
                 * First, deal with the hole before the allocated space
                 * that we found, if any.
index 7219207d58eb9c48e10bac30a54dd0f465f2509c..8e69b532962901555ba481df383465172b270890 100644 (file)
@@ -97,6 +97,13 @@ struct xfs_extent_free_item
  */
 #define XFS_BMAPI_ZERO         0x080
 
+/*
+ * Map the inode offset to the block given in ap->firstblock.  Primarily
+ * used for reflink.  The range must be in a hole, and this flag cannot be
+ * turned on with PREALLOC or CONVERT, and cannot be used on the attr fork.
+ */
+#define XFS_BMAPI_REMAP                0x100
+
 #define XFS_BMAPI_FLAGS \
        { XFS_BMAPI_ENTIRE,     "ENTIRE" }, \
        { XFS_BMAPI_METADATA,   "METADATA" }, \
@@ -105,7 +112,8 @@ struct xfs_extent_free_item
        { XFS_BMAPI_IGSTATE,    "IGSTATE" }, \
        { XFS_BMAPI_CONTIG,     "CONTIG" }, \
        { XFS_BMAPI_CONVERT,    "CONVERT" }, \
-       { XFS_BMAPI_ZERO,       "ZERO" }
+       { XFS_BMAPI_ZERO,       "ZERO" }, \
+       { XFS_BMAPI_REMAP,      "REMAP" }
 
 
 static inline int xfs_bmapi_aflag(int w)
index 9c9acef13616a10287293a3fba6cce8393baaa55..e44992a84d7fffddfc64c194802b3a62b3272d63 100644 (file)
@@ -2968,6 +2968,60 @@ TRACE_EVENT(xfs_refcount_finish_one_leftover,
                  __entry->new_len)
 );
 
+/* simple inode-based error/%ip tracepoint class */
+DECLARE_EVENT_CLASS(xfs_inode_error_class,
+       TP_PROTO(struct xfs_inode *ip, int error, unsigned long caller_ip),
+       TP_ARGS(ip, error, caller_ip),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(xfs_ino_t, ino)
+               __field(int, error)
+               __field(unsigned long, caller_ip)
+       ),
+       TP_fast_assign(
+               __entry->dev = VFS_I(ip)->i_sb->s_dev;
+               __entry->ino = ip->i_ino;
+               __entry->error = error;
+               __entry->caller_ip = caller_ip;
+       ),
+       TP_printk("dev %d:%d ino %llx error %d caller %ps",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 __entry->ino,
+                 __entry->error,
+                 (char *)__entry->caller_ip)
+);
+
+#define DEFINE_INODE_ERROR_EVENT(name) \
+DEFINE_EVENT(xfs_inode_error_class, name, \
+       TP_PROTO(struct xfs_inode *ip, int error, \
+                unsigned long caller_ip), \
+       TP_ARGS(ip, error, caller_ip))
+
+/* reflink allocator */
+TRACE_EVENT(xfs_bmap_remap_alloc,
+       TP_PROTO(struct xfs_inode *ip, xfs_fsblock_t fsbno,
+                xfs_extlen_t len),
+       TP_ARGS(ip, fsbno, len),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(xfs_ino_t, ino)
+               __field(xfs_fsblock_t, fsbno)
+               __field(xfs_extlen_t, len)
+       ),
+       TP_fast_assign(
+               __entry->dev = VFS_I(ip)->i_sb->s_dev;
+               __entry->ino = ip->i_ino;
+               __entry->fsbno = fsbno;
+               __entry->len = len;
+       ),
+       TP_printk("dev %d:%d ino 0x%llx fsbno 0x%llx len %x",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 __entry->ino,
+                 __entry->fsbno,
+                 __entry->len)
+);
+DEFINE_INODE_ERROR_EVENT(xfs_bmap_remap_alloc_error);
+
 #endif /* _TRACE_XFS_H */
 
 #undef TRACE_INCLUDE_PATH