Commit 6567af78 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'xfs-4.18-merge-3' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux

Pull xfs updates from Darrick Wong:
 "New features this cycle include the ability to relabel mounted
  filesystems, support for fallocated swapfiles, and using FUA for pure
  data O_DSYNC directio writes. With this cycle we begin to integrate
  online filesystem repair and refactor the growfs code in preparation
  for eventual subvolume support, though the road ahead for both
  features is quite long.

  There are also numerous refactorings of the iomap code to remove
  unnecessary log overhead, to disentangle some of the quota code, and
  to prepare for buffer head removal in a future upstream kernel.

  Metadata validation continues to improve, both in the hot path
  veifiers and the online filesystem check code. I anticipate sending a
  second pull request in a few days with more metadata validation
  improvements.

  This series has been run through a full xfstests run over the weekend
  and through a quick xfstests run against this morning's master, with
  no major failures reported.

  Summary:

   - Strengthen inode number and structure validation when allocating
     inodes.

   - Reduce pointless buffer allocations during cache miss

   - Use FUA for pure data O_DSYNC directio writes

   - Various iomap refactorings

   - Strengthen quota metadata verification to avoid unfixable broken
     quota

   - Make AGFL block freeing a deferred operation to avoid blowing out
     transaction reservations when running complex operations

   - Get rid of the log item descriptors to reduce log overhead

   - Fix various reflink bugs where inodes were double-joined to
     transactions

   - Don't issue discards when trimming unwritten extents

   - Refactor incore dquot initialization and retrieval interfaces

   - Fix some locking problmes in the quota scrub code

   - Strengthen btree structure checks in scrub code

   - Rewrite swapfile activation to use iomap and support unwritten
     extents

   - Make scrub exit to userspace sooner when corruptions or
     cross-referencing problems are found

   - Make scrub invoke the data fork scrubber directly on metadata
     inodes

   - Don't do background reclamation of post-eof and cow blocks when the
     fs is suspended

   - Fix secondary superblock buffer lifespan hinting

   - Refactor growfs to use table-dispatched functions instead of long
     stringy functions

   - Move growfs code to libxfs

   - Implement online fs label getting and setting

   - Introduce online filesystem repair (in a very limited capacity)

   - Fix unit conversion problems in the realtime freemap iteration
     functions

   - Various refactorings and cleanups in preparation to remove buffer
     heads in a future release

   - Reimplement the old bmap call with iomap

   - Remove direct buffer head accesses from seek hole/data

   - Various bug fixes"

* tag 'xfs-4.18-merge-3' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux: (121 commits)
  fs: use ->is_partially_uptodate in page_cache_seek_hole_data
  fs: remove the buffer_unwritten check in page_seek_hole_data
  fs: move page_cache_seek_hole_data to iomap.c
  xfs: use iomap_bmap
  iomap: add an iomap-based bmap implementation
  iomap: add a iomap_sector helper
  iomap: use __bio_add_page in iomap_dio_zero
  iomap: move IOMAP_F_BOUNDARY to gfs2
  iomap: fix the comment describing IOMAP_NOWAIT
  iomap: inline data should be an iomap type, not a flag
  mm: split ->readpages calls to avoid non-contiguous pages lists
  mm: return an unsigned int from __do_page_cache_readahead
  mm: give the 'ret' variable a better name __do_page_cache_readahead
  block: add a lower-level bio_add_page interface
  xfs: fix error handling in xfs_refcount_insert()
  xfs: fix xfs_rtalloc_rec units
  xfs: strengthen rtalloc query range checks
  xfs: xfs_rtbuf_get should check the bmapi_read results
  xfs: xfs_rtword_t should be unsigned, not signed
  dax: change bdev_dax_supported() to support boolean returns
  ...
parents 1434763c afd9d6a1
......@@ -296,7 +296,8 @@ Code Seq#(hex) Include File Comments
0x90 00 drivers/cdrom/sbpcd.h
0x92 00-0F drivers/usb/mon/mon_bin.c
0x93 60-7F linux/auto_fs.h
0x94 all fs/btrfs/ioctl.h
0x94 all fs/btrfs/ioctl.h Btrfs filesystem
and linux/fs.h some lifted to vfs/generic
0x97 00-7F fs/ceph/ioctl.h Ceph file system
0x99 00-0F 537-Addinboard driver
<mailto:buk@buks.ipn.de>
......
......@@ -774,7 +774,7 @@ int bio_add_pc_page(struct request_queue *q, struct bio *bio, struct page
return 0;
}
if (bio->bi_vcnt >= bio->bi_max_vecs)
if (bio_full(bio))
return 0;
/*
......@@ -822,52 +822,82 @@ int bio_add_pc_page(struct request_queue *q, struct bio *bio, struct page
EXPORT_SYMBOL(bio_add_pc_page);
/**
* bio_add_page - attempt to add page to bio
* @bio: destination bio
* @page: page to add
* @len: vec entry length
* @offset: vec entry offset
* __bio_try_merge_page - try appending data to an existing bvec.
* @bio: destination bio
* @page: page to add
* @len: length of the data to add
* @off: offset of the data in @page
*
* Attempt to add a page to the bio_vec maplist. This will only fail
* if either bio->bi_vcnt == bio->bi_max_vecs or it's a cloned bio.
* Try to add the data at @page + @off to the last bvec of @bio. This is a
* a useful optimisation for file systems with a block size smaller than the
* page size.
*
* Return %true on success or %false on failure.
*/
int bio_add_page(struct bio *bio, struct page *page,
unsigned int len, unsigned int offset)
bool __bio_try_merge_page(struct bio *bio, struct page *page,
unsigned int len, unsigned int off)
{
struct bio_vec *bv;
/*
* cloned bio must not modify vec list
*/
if (WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED)))
return 0;
return false;
/*
* For filesystems with a blocksize smaller than the pagesize
* we will often be called with the same page as last time and
* a consecutive offset. Optimize this special case.
*/
if (bio->bi_vcnt > 0) {
bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
if (page == bv->bv_page &&
offset == bv->bv_offset + bv->bv_len) {
if (page == bv->bv_page && off == bv->bv_offset + bv->bv_len) {
bv->bv_len += len;
goto done;
bio->bi_iter.bi_size += len;
return true;
}
}
return false;
}
EXPORT_SYMBOL_GPL(__bio_try_merge_page);
if (bio->bi_vcnt >= bio->bi_max_vecs)
return 0;
/**
* __bio_add_page - add page to a bio in a new segment
* @bio: destination bio
* @page: page to add
* @len: length of the data to add
* @off: offset of the data in @page
*
* Add the data at @page + @off to @bio as a new bvec. The caller must ensure
* that @bio has space for another bvec.
*/
void __bio_add_page(struct bio *bio, struct page *page,
unsigned int len, unsigned int off)
{
struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt];
bv = &bio->bi_io_vec[bio->bi_vcnt];
bv->bv_page = page;
bv->bv_len = len;
bv->bv_offset = offset;
WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED));
WARN_ON_ONCE(bio_full(bio));
bv->bv_page = page;
bv->bv_offset = off;
bv->bv_len = len;
bio->bi_vcnt++;
done:
bio->bi_iter.bi_size += len;
bio->bi_vcnt++;
}
EXPORT_SYMBOL_GPL(__bio_add_page);
/**
* bio_add_page - attempt to add page to bio
* @bio: destination bio
* @page: page to add
* @len: vec entry length
* @offset: vec entry offset
*
* Attempt to add a page to the bio_vec maplist. This will only fail
* if either bio->bi_vcnt == bio->bi_max_vecs or it's a cloned bio.
*/
int bio_add_page(struct bio *bio, struct page *page,
unsigned int len, unsigned int offset)
{
if (!__bio_try_merge_page(bio, page, len, offset)) {
if (bio_full(bio))
return 0;
__bio_add_page(bio, page, len, offset);
}
return len;
}
EXPORT_SYMBOL(bio_add_page);
......
......@@ -74,42 +74,42 @@ EXPORT_SYMBOL_GPL(fs_dax_get_by_bdev);
/**
* __bdev_dax_supported() - Check if the device supports dax for filesystem
* @sb: The superblock of the device
* @bdev: block device to check
* @blocksize: The block size of the device
*
* This is a library function for filesystems to check if the block device
* can be mounted with dax option.
*
* Return: negative errno if unsupported, 0 if supported.
* Return: true if supported, false if unsupported
*/
int __bdev_dax_supported(struct super_block *sb, int blocksize)
bool __bdev_dax_supported(struct block_device *bdev, int blocksize)
{
struct block_device *bdev = sb->s_bdev;
struct dax_device *dax_dev;
pgoff_t pgoff;
int err, id;
void *kaddr;
pfn_t pfn;
long len;
char buf[BDEVNAME_SIZE];
if (blocksize != PAGE_SIZE) {
pr_debug("VFS (%s): error: unsupported blocksize for dax\n",
sb->s_id);
return -EINVAL;
pr_debug("%s: error: unsupported blocksize for dax\n",
bdevname(bdev, buf));
return false;
}
err = bdev_dax_pgoff(bdev, 0, PAGE_SIZE, &pgoff);
if (err) {
pr_debug("VFS (%s): error: unaligned partition for dax\n",
sb->s_id);
return err;
pr_debug("%s: error: unaligned partition for dax\n",
bdevname(bdev, buf));
return false;
}
dax_dev = dax_get_by_host(bdev->bd_disk->disk_name);
if (!dax_dev) {
pr_debug("VFS (%s): error: device does not support dax\n",
sb->s_id);
return -EOPNOTSUPP;
pr_debug("%s: error: device does not support dax\n",
bdevname(bdev, buf));
return false;
}
id = dax_read_lock();
......@@ -119,9 +119,9 @@ int __bdev_dax_supported(struct super_block *sb, int blocksize)
put_dax(dax_dev);
if (len < 1) {
pr_debug("VFS (%s): error: dax access failed (%ld)\n",
sb->s_id, len);
return len < 0 ? len : -EIO;
pr_debug("%s: error: dax access failed (%ld)\n",
bdevname(bdev, buf), len);
return false;
}
if (IS_ENABLED(CONFIG_FS_DAX_LIMITED) && pfn_t_special(pfn)) {
......@@ -137,12 +137,12 @@ int __bdev_dax_supported(struct super_block *sb, int blocksize)
} else if (pfn_t_devmap(pfn)) {
/* pass */;
} else {
pr_debug("VFS (%s): error: dax support not enabled\n",
sb->s_id);
return -EOPNOTSUPP;
pr_debug("%s: error: dax support not enabled\n",
bdevname(bdev, buf));
return false;
}
return 0;
return true;
}
EXPORT_SYMBOL_GPL(__bdev_dax_supported);
#endif
......
......@@ -3427,120 +3427,6 @@ int bh_submit_read(struct buffer_head *bh)
}
EXPORT_SYMBOL(bh_submit_read);
/*
* Seek for SEEK_DATA / SEEK_HOLE within @page, starting at @lastoff.
*
* Returns the offset within the file on success, and -ENOENT otherwise.
*/
static loff_t
page_seek_hole_data(struct page *page, loff_t lastoff, int whence)
{
loff_t offset = page_offset(page);
struct buffer_head *bh, *head;
bool seek_data = whence == SEEK_DATA;
if (lastoff < offset)
lastoff = offset;
bh = head = page_buffers(page);
do {
offset += bh->b_size;
if (lastoff >= offset)
continue;
/*
* Unwritten extents that have data in the page cache covering
* them can be identified by the BH_Unwritten state flag.
* Pages with multiple buffers might have a mix of holes, data
* and unwritten extents - any buffer with valid data in it
* should have BH_Uptodate flag set on it.
*/
if ((buffer_unwritten(bh) || buffer_uptodate(bh)) == seek_data)
return lastoff;
lastoff = offset;
} while ((bh = bh->b_this_page) != head);
return -ENOENT;
}
/*
* Seek for SEEK_DATA / SEEK_HOLE in the page cache.
*
* Within unwritten extents, the page cache determines which parts are holes
* and which are data: unwritten and uptodate buffer heads count as data;
* everything else counts as a hole.
*
* Returns the resulting offset on successs, and -ENOENT otherwise.
*/
loff_t
page_cache_seek_hole_data(struct inode *inode, loff_t offset, loff_t length,
int whence)
{
pgoff_t index = offset >> PAGE_SHIFT;
pgoff_t end = DIV_ROUND_UP(offset + length, PAGE_SIZE);
loff_t lastoff = offset;
struct pagevec pvec;
if (length <= 0)
return -ENOENT;
pagevec_init(&pvec);
do {
unsigned nr_pages, i;
nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping, &index,
end - 1);
if (nr_pages == 0)
break;
for (i = 0; i < nr_pages; i++) {
struct page *page = pvec.pages[i];
/*
* At this point, the page may be truncated or
* invalidated (changing page->mapping to NULL), or
* even swizzled back from swapper_space to tmpfs file
* mapping. However, page->index will not change
* because we have a reference on the page.
*
* If current page offset is beyond where we've ended,
* we've found a hole.
*/
if (whence == SEEK_HOLE &&
lastoff < page_offset(page))
goto check_range;
lock_page(page);
if (likely(page->mapping == inode->i_mapping) &&
page_has_buffers(page)) {
lastoff = page_seek_hole_data(page, lastoff, whence);
if (lastoff >= 0) {
unlock_page(page);
goto check_range;
}
}
unlock_page(page);
lastoff = page_offset(page) + PAGE_SIZE;
}
pagevec_release(&pvec);
} while (index < end);
/* When no page at lastoff and we are not done, we found a hole. */
if (whence != SEEK_HOLE)
goto not_found;
check_range:
if (lastoff < offset + length)
goto out;
not_found:
lastoff = -ENOENT;
out:
pagevec_release(&pvec);
return lastoff;
}
void __init buffer_init(void)
{
unsigned long nrpages;
......
......@@ -961,8 +961,7 @@ static int ext2_fill_super(struct super_block *sb, void *data, int silent)
blocksize = BLOCK_SIZE << le32_to_cpu(sbi->s_es->s_log_block_size);
if (sbi->s_mount_opt & EXT2_MOUNT_DAX) {
err = bdev_dax_supported(sb, blocksize);
if (err) {
if (!bdev_dax_supported(sb->s_bdev, blocksize)) {
ext2_msg(sb, KERN_ERR,
"DAX unsupported by block device. Turning off DAX.");
sbi->s_mount_opt &= ~EXT2_MOUNT_DAX;
......
......@@ -1841,8 +1841,8 @@ int ext4_inline_data_iomap(struct inode *inode, struct iomap *iomap)
iomap->offset = 0;
iomap->length = min_t(loff_t, ext4_get_inline_size(inode),
i_size_read(inode));
iomap->type = 0;
iomap->flags = IOMAP_F_DATA_INLINE;
iomap->type = IOMAP_INLINE;
iomap->flags = 0;
out:
up_read(&EXT4_I(inode)->xattr_sem);
......
......@@ -3762,8 +3762,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
" that may contain inline data");
sbi->s_mount_opt &= ~EXT4_MOUNT_DAX;
}
err = bdev_dax_supported(sb, blocksize);
if (err) {
if (!bdev_dax_supported(sb->s_bdev, blocksize)) {
ext4_msg(sb, KERN_ERR,
"DAX unsupported by block device. Turning off DAX.");
sbi->s_mount_opt &= ~EXT4_MOUNT_DAX;
......
......@@ -767,10 +767,11 @@ static void gfs2_stuffed_iomap(struct inode *inode, struct iomap *iomap)
sizeof(struct gfs2_dinode);
iomap->offset = 0;
iomap->length = i_size_read(inode);
iomap->type = IOMAP_MAPPED;
iomap->flags = IOMAP_F_DATA_INLINE;
iomap->type = IOMAP_INLINE;
}
#define IOMAP_F_GFS2_BOUNDARY IOMAP_F_PRIVATE
/**
* gfs2_iomap_get - Map blocks from an inode to disk blocks
* @inode: The inode
......@@ -846,7 +847,7 @@ static int gfs2_iomap_get(struct inode *inode, loff_t pos, loff_t length,
iomap->type = IOMAP_MAPPED;
iomap->flags = IOMAP_F_MERGED;
if (eob)
iomap->flags |= IOMAP_F_BOUNDARY;
iomap->flags |= IOMAP_F_GFS2_BOUNDARY;
out:
iomap->bdev = inode->i_sb->s_bdev;
......@@ -952,12 +953,12 @@ int gfs2_block_map(struct inode *inode, sector_t lblock,
if (iomap.length > bh_map->b_size) {
iomap.length = bh_map->b_size;
iomap.flags &= ~IOMAP_F_BOUNDARY;
iomap.flags &= ~IOMAP_F_GFS2_BOUNDARY;
}
if (iomap.addr != IOMAP_NULL_ADDR)
map_bh(bh_map, inode->i_sb, iomap.addr >> inode->i_blkbits);
bh_map->b_size = iomap.length;
if (iomap.flags & IOMAP_F_BOUNDARY)
if (iomap.flags & IOMAP_F_GFS2_BOUNDARY)
set_buffer_boundary(bh_map);
if (iomap.flags & IOMAP_F_NEW)
set_buffer_new(bh_map);
......
This diff is collapsed.
......@@ -85,6 +85,24 @@ config XFS_ONLINE_SCRUB
If unsure, say N.
config XFS_ONLINE_REPAIR
bool "XFS online metadata repair support"
default n
depends on XFS_FS && XFS_ONLINE_SCRUB
help
If you say Y here you will be able to repair metadata on a
mounted XFS filesystem. This feature is intended to reduce
filesystem downtime by fixing minor problems before they cause the
filesystem to go down. However, it requires that the filesystem be
formatted with secondary metadata, such as reverse mappings and inode
parent pointers.
This feature is considered EXPERIMENTAL. Use with caution!
See the xfs_scrub man page in section 8 for additional information.
If unsure, say N.
config XFS_WARN
bool "XFS Verbose Warnings"
depends on XFS_FS && !XFS_DEBUG
......
......@@ -28,6 +28,7 @@ xfs-y += xfs_trace.o
# build the libxfs code first
xfs-y += $(addprefix libxfs/, \
xfs_ag.o \
xfs_alloc.o \
xfs_alloc_btree.o \
xfs_attr.o \
......@@ -163,4 +164,12 @@ xfs-y += $(addprefix scrub/, \
xfs-$(CONFIG_XFS_RT) += scrub/rtbitmap.o
xfs-$(CONFIG_XFS_QUOTA) += scrub/quota.o
# online repair
ifeq ($(CONFIG_XFS_ONLINE_REPAIR),y)
xfs-y += $(addprefix scrub/, \
agheader_repair.o \
repair.o \
)
endif
endif
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018 Red Hat, Inc.
* All rights reserved.
*/
#ifndef __LIBXFS_AG_H
#define __LIBXFS_AG_H 1
struct xfs_mount;
struct xfs_trans;
struct aghdr_init_data {
/* per ag data */
xfs_agblock_t agno; /* ag to init */
xfs_extlen_t agsize; /* new AG size */
struct list_head buffer_list; /* buffer writeback list */
xfs_rfsblock_t nfree; /* cumulative new free space */
/* per header data */
xfs_daddr_t daddr; /* header location */
size_t numblks; /* size of header */
xfs_btnum_t type; /* type of btree root block */
};
int xfs_ag_init_headers(struct xfs_mount *mp, struct aghdr_init_data *id);
int xfs_ag_extend_space(struct xfs_mount *mp, struct xfs_trans *tp,
struct aghdr_init_data *id, xfs_extlen_t len);
#endif /* __LIBXFS_AG_H */
......@@ -39,6 +39,9 @@
#include "xfs_buf_item.h"
#include "xfs_log.h"
#include "xfs_ag_resv.h"
#include "xfs_bmap.h"
extern kmem_zone_t *xfs_bmap_free_item_zone;
struct workqueue_struct *xfs_alloc_wq;
......@@ -2060,6 +2063,30 @@ xfs_alloc_space_available(
return true;
}
int
xfs_free_agfl_block(
struct xfs_trans *tp,
xfs_agnumber_t agno,
xfs_agblock_t agbno,
struct xfs_buf *agbp,
struct xfs_owner_info *oinfo)
{
int error;
struct xfs_buf *bp;
error = xfs_free_ag_extent(tp, agbp, agno, agbno, 1, oinfo,
XFS_AG_RESV_AGFL);
if (error)
return error;
bp = xfs_btree_get_bufs(tp->t_mountp, tp, agno, agbno, 0);
if (!bp)
return -EFSCORRUPTED;
xfs_trans_binval(tp, bp);
return 0;
}
/*
* Check the agfl fields of the agf for inconsistency or corruption. The purpose
* is to detect an agfl header padding mismatch between current and early v5
......@@ -2147,6 +2174,40 @@ xfs_agfl_reset(
pag->pagf_agflreset = false;
}
/*
* Defer an AGFL block free. This is effectively equivalent to
* xfs_bmap_add_free() with some special handling particular to AGFL blocks.
*
* Deferring AGFL frees helps prevent log reservation overruns due to too many
* allocation operations in a transaction. AGFL frees are prone to this problem
* because for one they are always freed one at a time. Further, an immediate
* AGFL block free can cause a btree join and require another block free before
* the real allocation can proceed. Deferring the free disconnects freeing up
* the AGFL slot from freeing the block.
*/
STATIC void
xfs_defer_agfl_block(
struct xfs_mount *mp,
struct xfs_defer_ops *dfops,
xfs_agnumber_t agno,
xfs_fsblock_t agbno,
struct xfs_owner_info *oinfo)
{
struct xfs_extent_free_item *new; /* new element */
ASSERT(xfs_bmap_free_item_zone != NULL);
ASSERT(oinfo != NULL);
new = kmem_zone_alloc(xfs_bmap_free_item_zone, KM_SLEEP);
new->xefi_startblock = XFS_AGB_TO_FSB(mp, agno, agbno);
new->xefi_blockcount = 1;
new->xefi_oinfo = *oinfo;
trace_xfs_agfl_free_defer(mp, agno, 0, agbno, 1);
xfs_defer_add(dfops, XFS_DEFER_OPS_TYPE_AGFL_FREE, &new->xefi_list);
}
/*
* Decide whether to use this allocation group for this allocation.
* If so, fix up the btree freelist's size.
......@@ -2247,21 +2308,20 @@ xfs_alloc_fix_freelist(
else
xfs_rmap_ag_owner(&targs.oinfo, XFS_RMAP_OWN_AG);
while (!(flags & XFS_ALLOC_FLAG_NOSHRINK) && pag->pagf_flcount > need) {
struct xfs_buf *bp;
error = xfs_alloc_get_freelist(tp, agbp, &bno, 0);
if (error)
goto out_agbp_relse;
error = xfs_free_ag_extent(tp, agbp, args->agno, bno, 1,
&targs.oinfo, XFS_AG_RESV_AGFL);
if (error)
goto out_agbp_relse;
bp = xfs_btree_get_bufs(mp, tp, args->agno, bno, 0);
if (!bp) {
error = -EFSCORRUPTED;
goto out_agbp_relse;
/* defer agfl frees if dfops is provided */
if (tp->t_agfl_dfops) {
xfs_defer_agfl_block(mp, tp->t_agfl_dfops, args->agno,
bno, &targs.oinfo);
} else {
error = xfs_free_agfl_block(tp, args->agno, bno, agbp,
&targs.oinfo);
if (error)
goto out_agbp_relse;
}
xfs_trans_binval(tp, bp);
}
targs.tp = tp;
......@@ -2949,18 +3009,20 @@ xfs_free_extent_fix_freelist(
* after fixing up the freelist.
*/
int /* error */
xfs_free_extent(
__xfs_free_extent(
struct xfs_trans *tp, /* transaction pointer */
xfs_fsblock_t bno, /* starting block number of extent */
xfs_extlen_t len, /* length of extent */
struct xfs_owner_info *oinfo, /* extent owner */
enum xfs_ag_resv_type type) /* block reservation type */
enum xfs_ag_resv_type type, /* block reservation type */
bool skip_discard)
{
struct xfs_mount *mp = tp->t_mountp;
struct xfs_buf *agbp;
xfs_agnumber_t agno = XFS_FSB_TO_AGNO(mp, bno);
xfs_agblock_t agbno = XFS_FSB_TO_AGBNO(mp, bno);
int error;
unsigned int busy_flags = 0;
ASSERT(len != 0);
ASSERT(type != XFS_AG_RESV_AGFL);
......@@ -2984,7 +3046,9 @@ xfs_free_extent(
if (error)
goto err;
xfs_extent_busy_insert(tp, agno, agbno, len, 0);
if (skip_discard)
busy_flags |= XFS_EXTENT_BUSY_SKIP_DISCARD;
xfs_extent_busy_insert(tp, agno, agbno, len, busy_flags);
return 0;
err:
......@@ -3116,3 +3180,40 @@ xfs_alloc_has_record(
return xfs_btree_has_record(cur, &low, &high, exists);
}
/*
* Walk all the blocks in the AGFL. The @walk_fn can return any negative
* error code or XFS_BTREE_QUERY_RANGE_ABORT.
*/
int
xfs_agfl_walk(
struct xfs_mount *mp,
struct xfs_agf *agf,
struct xfs_buf *agflbp,
xfs_agfl_walk_fn walk_fn,
void *priv)
{
__be32 *agfl_bno;
unsigned int i;
int error;
agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, agflbp);
i = be32_to_cpu(agf->agf_flfirst);
/* Nothing to walk in an empty AGFL. */
if (agf->agf_flcount == cpu_to_be32(0))
return 0;
/* Otherwise, walk from first to last, wrapping as needed. */
for (;;) {
error = walk_fn(mp, be32_to_cpu(agfl_bno[i]), priv);
if (error)
return error;
if (i == be32_to_cpu(agf->agf_fllast))
break;
if (++i == xfs_agfl_size(mp))
i = 0;
}
return 0;
}
......@@ -191,12 +191,24 @@ xfs_alloc_vextent(
* Free an extent.
*/
int /* error */
xfs_free_extent(