Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

xfs: make COW fork unwritten extent conversions more robust

If we have racing buffered and direct I/O COW fork extents under
writeback can have been moved to the data fork by the time we call
xfs_reflink_convert_cow from xfs_submit_ioend. This would be mostly
harmless as the block numbers don't change by this move, except for
the fact that xfs_bmapi_write will crash or trigger asserts when
not finding existing extents, even despite trying to paper over this
with the XFS_BMAPI_CONVERT_ONLY flag.

Instead of special casing non-transaction conversions in the already
way too complicated xfs_bmapi_write just add a new helper for the much
simpler non-transactional COW fork case, which simplify ignores not
found extents.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>

authored by

Christoph Hellwig and committed by
Darrick J. Wong
26b91c72 db46e604

+45 -33
+3 -6
fs/xfs/libxfs/xfs_bmap.c
··· 2031 2031 /* 2032 2032 * Convert an unwritten allocation to a real allocation or vice versa. 2033 2033 */ 2034 - STATIC int /* error */ 2034 + int /* error */ 2035 2035 xfs_bmap_add_extent_unwritten_real( 2036 2036 struct xfs_trans *tp, 2037 2037 xfs_inode_t *ip, /* incore inode pointer */ ··· 4276 4276 4277 4277 ASSERT(*nmap >= 1); 4278 4278 ASSERT(*nmap <= XFS_BMAP_MAX_NMAP); 4279 - ASSERT(tp != NULL || 4280 - (flags & (XFS_BMAPI_CONVERT | XFS_BMAPI_COWFORK)) == 4281 - (XFS_BMAPI_CONVERT | XFS_BMAPI_COWFORK)); 4279 + ASSERT(tp != NULL); 4282 4280 ASSERT(len > 0); 4283 4281 ASSERT(XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL); 4284 4282 ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL)); ··· 4350 4352 * First, deal with the hole before the allocated space 4351 4353 * that we found, if any. 4352 4354 */ 4353 - if ((need_alloc || wasdelay) && 4354 - !(flags & XFS_BMAPI_CONVERT_ONLY)) { 4355 + if (need_alloc || wasdelay) { 4355 4356 bma.eof = eof; 4356 4357 bma.conv = !!(flags & XFS_BMAPI_CONVERT); 4357 4358 bma.wasdel = wasdelay;
+4 -4
fs/xfs/libxfs/xfs_bmap.h
··· 95 95 /* Map something in the CoW fork. */ 96 96 #define XFS_BMAPI_COWFORK 0x200 97 97 98 - /* Only convert unwritten extents, don't allocate new blocks */ 99 - #define XFS_BMAPI_CONVERT_ONLY 0x800 100 - 101 98 /* Skip online discard of freed extents */ 102 99 #define XFS_BMAPI_NODISCARD 0x1000 103 100 ··· 111 114 { XFS_BMAPI_ZERO, "ZERO" }, \ 112 115 { XFS_BMAPI_REMAP, "REMAP" }, \ 113 116 { XFS_BMAPI_COWFORK, "COWFORK" }, \ 114 - { XFS_BMAPI_CONVERT_ONLY, "CONVERT_ONLY" }, \ 115 117 { XFS_BMAPI_NODISCARD, "NODISCARD" }, \ 116 118 { XFS_BMAPI_NORMAP, "NORMAP" } 117 119 ··· 222 226 int xfs_bmapi_convert_delalloc(struct xfs_inode *ip, int whichfork, 223 227 xfs_fileoff_t offset_fsb, struct xfs_bmbt_irec *imap, 224 228 unsigned int *seq); 229 + int xfs_bmap_add_extent_unwritten_real(struct xfs_trans *tp, 230 + struct xfs_inode *ip, int whichfork, 231 + struct xfs_iext_cursor *icur, struct xfs_btree_cur **curp, 232 + struct xfs_bmbt_irec *new, int *logflagsp); 225 233 226 234 static inline void 227 235 xfs_bmap_add_free(
+38 -23
fs/xfs/xfs_reflink.c
··· 234 234 } 235 235 } 236 236 237 - /* Convert part of an unwritten CoW extent to a real one. */ 238 - STATIC int 239 - xfs_reflink_convert_cow_extent( 240 - struct xfs_inode *ip, 241 - struct xfs_bmbt_irec *imap, 242 - xfs_fileoff_t offset_fsb, 243 - xfs_filblks_t count_fsb) 237 + static int 238 + xfs_reflink_convert_cow_locked( 239 + struct xfs_inode *ip, 240 + xfs_fileoff_t offset_fsb, 241 + xfs_filblks_t count_fsb) 244 242 { 245 - int nimaps = 1; 243 + struct xfs_iext_cursor icur; 244 + struct xfs_bmbt_irec got; 245 + struct xfs_btree_cur *dummy_cur = NULL; 246 + int dummy_logflags; 247 + int error; 246 248 247 - if (imap->br_state == XFS_EXT_NORM) 249 + if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &got)) 248 250 return 0; 249 251 250 - xfs_trim_extent(imap, offset_fsb, count_fsb); 251 - trace_xfs_reflink_convert_cow(ip, imap); 252 - if (imap->br_blockcount == 0) 253 - return 0; 254 - return xfs_bmapi_write(NULL, ip, imap->br_startoff, imap->br_blockcount, 255 - XFS_BMAPI_COWFORK | XFS_BMAPI_CONVERT, 0, imap, 256 - &nimaps); 252 + do { 253 + if (got.br_startoff >= offset_fsb + count_fsb) 254 + break; 255 + if (got.br_state == XFS_EXT_NORM) 256 + continue; 257 + if (WARN_ON_ONCE(isnullstartblock(got.br_startblock))) 258 + return -EIO; 259 + 260 + xfs_trim_extent(&got, offset_fsb, count_fsb); 261 + if (!got.br_blockcount) 262 + continue; 263 + 264 + got.br_state = XFS_EXT_NORM; 265 + error = xfs_bmap_add_extent_unwritten_real(NULL, ip, 266 + XFS_COW_FORK, &icur, &dummy_cur, &got, 267 + &dummy_logflags); 268 + if (error) 269 + return error; 270 + } while (xfs_iext_next_extent(ip->i_cowfp, &icur, &got)); 271 + 272 + return error; 257 273 } 258 274 259 275 /* Convert all of the unwritten CoW extents in a file's range to real ones. */ ··· 283 267 xfs_fileoff_t offset_fsb = XFS_B_TO_FSBT(mp, offset); 284 268 xfs_fileoff_t end_fsb = XFS_B_TO_FSB(mp, offset + count); 285 269 xfs_filblks_t count_fsb = end_fsb - offset_fsb; 286 - struct xfs_bmbt_irec imap; 287 - int nimaps = 1, error = 0; 270 + int error; 288 271 289 272 ASSERT(count != 0); 290 273 291 274 xfs_ilock(ip, XFS_ILOCK_EXCL); 292 - error = xfs_bmapi_write(NULL, ip, offset_fsb, count_fsb, 293 - XFS_BMAPI_COWFORK | XFS_BMAPI_CONVERT | 294 - XFS_BMAPI_CONVERT_ONLY, 0, &imap, &nimaps); 275 + error = xfs_reflink_convert_cow_locked(ip, offset_fsb, count_fsb); 295 276 xfs_iunlock(ip, XFS_ILOCK_EXCL); 296 277 return error; 297 278 } ··· 418 405 if (nimaps == 0) 419 406 return -ENOSPC; 420 407 convert: 408 + xfs_trim_extent(imap, offset_fsb, count_fsb); 421 409 /* 422 410 * COW fork extents are supposed to remain unwritten until we're ready 423 411 * to initiate a disk write. For direct I/O we are going to write the 424 412 * data and need the conversion, but for buffered writes we're done. 425 413 */ 426 - if (!(iomap_flags & IOMAP_DIRECT)) 414 + if (!(iomap_flags & IOMAP_DIRECT) || imap->br_state == XFS_EXT_NORM) 427 415 return 0; 428 - return xfs_reflink_convert_cow_extent(ip, imap, offset_fsb, count_fsb); 416 + trace_xfs_reflink_convert_cow(ip, imap); 417 + return xfs_reflink_convert_cow_locked(ip, offset_fsb, count_fsb); 429 418 430 419 out_unreserve: 431 420 xfs_trans_unreserve_quota_nblks(tp, ip, (long)resblks, 0,