mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
Now that the VFS inode has a u64 i_ino field, there is no need to store a copy of the inode number in the xfs_inode structure. Introduce an I_INO() wrapper as a shortcut to the inode number so that we don't have to propagate the VFS inode everywhere. The only non-obvious part is the clearing of i_ino to 0 for RCU freeing the inode. None of this calls into VFS paths, which makes clearing the VFS inode field here just as safe as clearing the old field in the xfs_inode. Signed-off-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Signed-off-by: Carlos Maiolino <cem@kernel.org>
422 lines
10 KiB
C
422 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2022-2023 Oracle. All Rights Reserved.
|
|
* Author: Darrick J. Wong <djwong@kernel.org>
|
|
*/
|
|
#include "xfs_platform.h"
|
|
#include "xfs_fs.h"
|
|
#include "xfs_shared.h"
|
|
#include "xfs_format.h"
|
|
#include "xfs_log_format.h"
|
|
#include "xfs_trans_resv.h"
|
|
#include "xfs_mount.h"
|
|
#include "xfs_inode.h"
|
|
#include "xfs_dir2.h"
|
|
#include "xfs_dir2_priv.h"
|
|
#include "xfs_trace.h"
|
|
#include "xfs_bmap.h"
|
|
#include "xfs_trans.h"
|
|
#include "xfs_error.h"
|
|
#include "scrub/scrub.h"
|
|
#include "scrub/common.h"
|
|
#include "scrub/readdir.h"
|
|
|
|
/* Call a function for every entry in a shortform directory. */
|
|
STATIC int
|
|
xchk_dir_walk_sf(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp,
|
|
xchk_dirent_fn dirent_fn,
|
|
void *priv)
|
|
{
|
|
struct xfs_name name = {
|
|
.name = ".",
|
|
.len = 1,
|
|
.type = XFS_DIR3_FT_DIR,
|
|
};
|
|
struct xfs_mount *mp = dp->i_mount;
|
|
struct xfs_da_geometry *geo = mp->m_dir_geo;
|
|
struct xfs_dir2_sf_entry *sfep;
|
|
struct xfs_dir2_sf_hdr *sfp = dp->i_df.if_data;
|
|
xfs_ino_t ino;
|
|
xfs_dir2_dataptr_t dapos;
|
|
unsigned int i;
|
|
int error;
|
|
|
|
ASSERT(dp->i_df.if_bytes == dp->i_disk_size);
|
|
ASSERT(sfp != NULL);
|
|
|
|
/* dot entry */
|
|
dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
|
|
geo->data_entry_offset);
|
|
|
|
error = dirent_fn(sc, dp, dapos, &name, I_INO(dp), priv);
|
|
if (error)
|
|
return error;
|
|
|
|
/* dotdot entry */
|
|
dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
|
|
geo->data_entry_offset +
|
|
xfs_dir2_data_entsize(mp, sizeof(".") - 1));
|
|
ino = xfs_dir2_sf_get_parent_ino(sfp);
|
|
name.name = "..";
|
|
name.len = 2;
|
|
|
|
error = dirent_fn(sc, dp, dapos, &name, ino, priv);
|
|
if (error)
|
|
return error;
|
|
|
|
/* iterate everything else */
|
|
sfep = xfs_dir2_sf_firstentry(sfp);
|
|
for (i = 0; i < sfp->count; i++) {
|
|
dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
|
|
xfs_dir2_sf_get_offset(sfep));
|
|
ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
|
|
name.name = sfep->name;
|
|
name.len = sfep->namelen;
|
|
name.type = xfs_dir2_sf_get_ftype(mp, sfep);
|
|
|
|
error = dirent_fn(sc, dp, dapos, &name, ino, priv);
|
|
if (error)
|
|
return error;
|
|
|
|
sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Call a function for every entry in a block directory. */
|
|
STATIC int
|
|
xchk_dir_walk_block(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp,
|
|
xchk_dirent_fn dirent_fn,
|
|
void *priv)
|
|
{
|
|
struct xfs_mount *mp = dp->i_mount;
|
|
struct xfs_da_geometry *geo = mp->m_dir_geo;
|
|
struct xfs_buf *bp;
|
|
unsigned int off, next_off, end;
|
|
int error;
|
|
|
|
error = xfs_dir3_block_read(sc->tp, dp, I_INO(dp), &bp);
|
|
if (error)
|
|
return error;
|
|
|
|
/* Walk each directory entry. */
|
|
end = xfs_dir3_data_end_offset(geo, bp->b_addr);
|
|
for (off = geo->data_entry_offset; off < end; off = next_off) {
|
|
struct xfs_name name = { };
|
|
struct xfs_dir2_data_unused *dup = bp->b_addr + off;
|
|
struct xfs_dir2_data_entry *dep = bp->b_addr + off;
|
|
xfs_ino_t ino;
|
|
xfs_dir2_dataptr_t dapos;
|
|
|
|
/* Skip an empty entry. */
|
|
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
|
|
next_off = off + be16_to_cpu(dup->length);
|
|
continue;
|
|
}
|
|
|
|
/* Otherwise, find the next entry and report it. */
|
|
next_off = off + xfs_dir2_data_entsize(mp, dep->namelen);
|
|
if (next_off > end)
|
|
break;
|
|
|
|
dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, off);
|
|
ino = be64_to_cpu(dep->inumber);
|
|
name.name = dep->name;
|
|
name.len = dep->namelen;
|
|
name.type = xfs_dir2_data_get_ftype(mp, dep);
|
|
|
|
error = dirent_fn(sc, dp, dapos, &name, ino, priv);
|
|
if (error)
|
|
break;
|
|
}
|
|
|
|
xfs_trans_brelse(sc->tp, bp);
|
|
return error;
|
|
}
|
|
|
|
/* Read a leaf-format directory buffer. */
|
|
STATIC int
|
|
xchk_read_leaf_dir_buf(
|
|
struct xfs_trans *tp,
|
|
struct xfs_inode *dp,
|
|
struct xfs_da_geometry *geo,
|
|
xfs_dir2_off_t *curoff,
|
|
struct xfs_buf **bpp)
|
|
{
|
|
struct xfs_iext_cursor icur;
|
|
struct xfs_bmbt_irec map;
|
|
struct xfs_ifork *ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK);
|
|
xfs_dablk_t last_da;
|
|
xfs_dablk_t map_off;
|
|
xfs_dir2_off_t new_off;
|
|
|
|
*bpp = NULL;
|
|
|
|
/*
|
|
* Look for mapped directory blocks at or above the current offset.
|
|
* Truncate down to the nearest directory block to start the scanning
|
|
* operation.
|
|
*/
|
|
last_da = xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET);
|
|
map_off = xfs_dir2_db_to_da(geo, xfs_dir2_byte_to_db(geo, *curoff));
|
|
|
|
if (!xfs_iext_lookup_extent(dp, ifp, map_off, &icur, &map))
|
|
return 0;
|
|
if (map.br_startoff >= last_da)
|
|
return 0;
|
|
xfs_trim_extent(&map, map_off, last_da - map_off);
|
|
|
|
/* Read the directory block of that first mapping. */
|
|
new_off = xfs_dir2_da_to_byte(geo, map.br_startoff);
|
|
if (new_off > *curoff)
|
|
*curoff = new_off;
|
|
|
|
return xfs_dir3_data_read(tp, dp, I_INO(dp), map.br_startoff, 0, bpp);
|
|
}
|
|
|
|
/* Call a function for every entry in a leaf directory. */
|
|
STATIC int
|
|
xchk_dir_walk_leaf(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp,
|
|
xchk_dirent_fn dirent_fn,
|
|
void *priv)
|
|
{
|
|
struct xfs_mount *mp = dp->i_mount;
|
|
struct xfs_da_geometry *geo = mp->m_dir_geo;
|
|
struct xfs_buf *bp = NULL;
|
|
xfs_dir2_off_t curoff = 0;
|
|
unsigned int offset = 0;
|
|
int error;
|
|
|
|
/* Iterate every directory offset in this directory. */
|
|
while (curoff < XFS_DIR2_LEAF_OFFSET) {
|
|
struct xfs_name name = { };
|
|
struct xfs_dir2_data_unused *dup;
|
|
struct xfs_dir2_data_entry *dep;
|
|
xfs_ino_t ino;
|
|
unsigned int length;
|
|
xfs_dir2_dataptr_t dapos;
|
|
|
|
/*
|
|
* If we have no buffer, or we're off the end of the
|
|
* current buffer, need to get another one.
|
|
*/
|
|
if (!bp || offset >= geo->blksize) {
|
|
if (bp) {
|
|
xfs_trans_brelse(sc->tp, bp);
|
|
bp = NULL;
|
|
}
|
|
|
|
error = xchk_read_leaf_dir_buf(sc->tp, dp, geo, &curoff,
|
|
&bp);
|
|
if (error || !bp)
|
|
break;
|
|
|
|
/*
|
|
* Find our position in the block.
|
|
*/
|
|
offset = geo->data_entry_offset;
|
|
curoff += geo->data_entry_offset;
|
|
}
|
|
|
|
/* Skip an empty entry. */
|
|
dup = bp->b_addr + offset;
|
|
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
|
|
length = be16_to_cpu(dup->length);
|
|
offset += length;
|
|
curoff += length;
|
|
continue;
|
|
}
|
|
|
|
/* Otherwise, find the next entry and report it. */
|
|
dep = bp->b_addr + offset;
|
|
length = xfs_dir2_data_entsize(mp, dep->namelen);
|
|
|
|
dapos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff;
|
|
ino = be64_to_cpu(dep->inumber);
|
|
name.name = dep->name;
|
|
name.len = dep->namelen;
|
|
name.type = xfs_dir2_data_get_ftype(mp, dep);
|
|
|
|
error = dirent_fn(sc, dp, dapos, &name, ino, priv);
|
|
if (error)
|
|
break;
|
|
|
|
/* Advance to the next entry. */
|
|
offset += length;
|
|
curoff += length;
|
|
}
|
|
|
|
if (bp)
|
|
xfs_trans_brelse(sc->tp, bp);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Call a function for every entry in a directory.
|
|
*
|
|
* Callers must hold the ILOCK. File types are XFS_DIR3_FT_*.
|
|
*/
|
|
int
|
|
xchk_dir_walk(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp,
|
|
xchk_dirent_fn dirent_fn,
|
|
void *priv)
|
|
{
|
|
struct xfs_da_args args = {
|
|
.dp = dp,
|
|
.geo = dp->i_mount->m_dir_geo,
|
|
.trans = sc->tp,
|
|
.owner = I_INO(dp),
|
|
};
|
|
int error;
|
|
|
|
if (xfs_is_shutdown(dp->i_mount))
|
|
return -EIO;
|
|
|
|
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
|
|
xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);
|
|
|
|
switch (xfs_dir2_format(&args, &error)) {
|
|
case XFS_DIR2_FMT_SF:
|
|
return xchk_dir_walk_sf(sc, dp, dirent_fn, priv);
|
|
case XFS_DIR2_FMT_BLOCK:
|
|
return xchk_dir_walk_block(sc, dp, dirent_fn, priv);
|
|
case XFS_DIR2_FMT_LEAF:
|
|
case XFS_DIR2_FMT_NODE:
|
|
return xchk_dir_walk_leaf(sc, dp, dirent_fn, priv);
|
|
default:
|
|
return error;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look up the inode number for an exact name in a directory.
|
|
*
|
|
* Callers must hold the ILOCK. File types are XFS_DIR3_FT_*. Names are not
|
|
* checked for correctness.
|
|
*/
|
|
int
|
|
xchk_dir_lookup(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp,
|
|
const struct xfs_name *name,
|
|
xfs_ino_t *ino)
|
|
{
|
|
struct xfs_da_args args = {
|
|
.dp = dp,
|
|
.geo = dp->i_mount->m_dir_geo,
|
|
.trans = sc->tp,
|
|
.name = name->name,
|
|
.namelen = name->len,
|
|
.filetype = name->type,
|
|
.hashval = xfs_dir2_hashname(dp->i_mount, name),
|
|
.whichfork = XFS_DATA_FORK,
|
|
.op_flags = XFS_DA_OP_OKNOENT,
|
|
.owner = I_INO(dp),
|
|
};
|
|
int error;
|
|
|
|
if (xfs_is_shutdown(dp->i_mount))
|
|
return -EIO;
|
|
|
|
/*
|
|
* A temporary directory's block headers are written with the owner
|
|
* set to sc->ip, so we must switch the owner here for the lookup.
|
|
*/
|
|
if (dp == sc->tempip)
|
|
args.owner = I_INO(sc->ip);
|
|
|
|
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
|
|
xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);
|
|
|
|
error = xfs_dir_lookup_args(&args);
|
|
if (!error)
|
|
*ino = args.inumber;
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Try to grab the IOLOCK and ILOCK of sc->ip and ip, returning @ip's lock
|
|
* state. The caller may have a transaction, so we must use trylock for both
|
|
* IOLOCKs.
|
|
*/
|
|
static inline unsigned int
|
|
xchk_dir_trylock_both(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *ip)
|
|
{
|
|
if (!xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
|
|
return 0;
|
|
|
|
if (!xfs_ilock_nowait(ip, XFS_IOLOCK_SHARED))
|
|
goto parent_iolock;
|
|
|
|
xchk_ilock(sc, XFS_ILOCK_EXCL);
|
|
if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))
|
|
goto parent_ilock;
|
|
|
|
return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
|
|
|
|
parent_ilock:
|
|
xchk_iunlock(sc, XFS_ILOCK_EXCL);
|
|
xfs_iunlock(ip, XFS_IOLOCK_SHARED);
|
|
parent_iolock:
|
|
xchk_iunlock(sc, XFS_IOLOCK_EXCL);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Try for a limited time to grab the IOLOCK and ILOCK of both the scrub target
|
|
* (@sc->ip) and the inode at the other end (@ip) of a directory or parent
|
|
* pointer link so that we can check that link.
|
|
*
|
|
* We do not know ahead of time that the directory tree is /not/ corrupt, so we
|
|
* cannot use the "lock two inode" functions because we do not know that there
|
|
* is not a racing thread trying to take the locks in opposite order. First
|
|
* take IOLOCK_EXCL of the scrub target, and then try to take IOLOCK_SHARED
|
|
* of @ip to synchronize with the VFS. Next, take ILOCK_EXCL of the scrub
|
|
* target and @ip to synchronize with XFS.
|
|
*
|
|
* If the trylocks succeed, *lockmode will be set to the locks held for @ip;
|
|
* @sc->ilock_flags will be set for the locks held for @sc->ip; and zero will
|
|
* be returned. If not, returns -EDEADLOCK to try again; or -ETIMEDOUT if
|
|
* XCHK_TRY_HARDER was set. Returns -EINTR if the process has been killed.
|
|
*/
|
|
int
|
|
xchk_dir_trylock_for_pptrs(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *ip,
|
|
unsigned int *lockmode)
|
|
{
|
|
unsigned int nr;
|
|
int error = 0;
|
|
|
|
ASSERT(sc->ilock_flags == 0);
|
|
|
|
for (nr = 0; nr < HZ; nr++) {
|
|
*lockmode = xchk_dir_trylock_both(sc, ip);
|
|
if (*lockmode)
|
|
return 0;
|
|
|
|
if (xchk_should_terminate(sc, &error))
|
|
return error;
|
|
|
|
delay(1);
|
|
}
|
|
|
|
if (sc->flags & XCHK_TRY_HARDER) {
|
|
xchk_set_incomplete(sc);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return -EDEADLOCK;
|
|
}
|