mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-12 19:06:40 +07:00
32c5483a8a
Lots of the dir code now goes through switches to determine what is the correct on-disk format to parse. It generally involves a "xfs_sbversion_hasfoo" check, deferencing the superblock version and feature fields and hence touching several cache lines per operation in the process. Some operations do multiple checks because they nest conditional operations and they don't pass the information in a direct fashion between each other. Hence, add an ops vector to the xfs_inode structure that is configured when the inode is initialised to point to all the correct decode and encoding operations. This will significantly reduce the branchiness and cacheline footprint of the directory object decoding and encoding. This is the first patch in a series of conversion patches. It will introduce the ops structure, the setup of it and add the first operation to the vector. Subsequent patches will convert directory ops one at a time to keep the changes simple and obvious. Just this patch shows the benefit of such an approach on code size. Just converting the two shortform dir operations as this patch does decreases the built binary size by ~1500 bytes: $ size fs/xfs/xfs.o.orig fs/xfs/xfs.o.p1 text data bss dec hex filename 794490 96802 1096 892388 d9de4 fs/xfs/xfs.o.orig 792986 96802 1096 890884 d9804 fs/xfs/xfs.o.p1 $ That's a significant decrease in the instruction cache footprint of the directory code for such a simple change, and indicates that this approach is definitely worth pursuing further. Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Ben Myers <bpm@sgi.com>
647 lines
16 KiB
C
647 lines
16 KiB
C
/*
|
|
* Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it would be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#include "xfs.h"
|
|
#include "xfs_fs.h"
|
|
#include "xfs_format.h"
|
|
#include "xfs_log_format.h"
|
|
#include "xfs_trans_resv.h"
|
|
#include "xfs_inum.h"
|
|
#include "xfs_sb.h"
|
|
#include "xfs_ag.h"
|
|
#include "xfs_mount.h"
|
|
#include "xfs_da_format.h"
|
|
#include "xfs_da_btree.h"
|
|
#include "xfs_inode.h"
|
|
#include "xfs_trans.h"
|
|
#include "xfs_inode_item.h"
|
|
#include "xfs_bmap.h"
|
|
#include "xfs_dir2.h"
|
|
#include "xfs_dir2_priv.h"
|
|
#include "xfs_error.h"
|
|
#include "xfs_trace.h"
|
|
#include "xfs_dinode.h"
|
|
|
|
struct xfs_name xfs_name_dotdot = { (unsigned char *)"..", 2, XFS_DIR3_FT_DIR };
|
|
|
|
|
|
/*
|
|
* ASCII case-insensitive (ie. A-Z) support for directories that was
|
|
* used in IRIX.
|
|
*/
|
|
STATIC xfs_dahash_t
|
|
xfs_ascii_ci_hashname(
|
|
struct xfs_name *name)
|
|
{
|
|
xfs_dahash_t hash;
|
|
int i;
|
|
|
|
for (i = 0, hash = 0; i < name->len; i++)
|
|
hash = tolower(name->name[i]) ^ rol32(hash, 7);
|
|
|
|
return hash;
|
|
}
|
|
|
|
STATIC enum xfs_dacmp
|
|
xfs_ascii_ci_compname(
|
|
struct xfs_da_args *args,
|
|
const unsigned char *name,
|
|
int len)
|
|
{
|
|
enum xfs_dacmp result;
|
|
int i;
|
|
|
|
if (args->namelen != len)
|
|
return XFS_CMP_DIFFERENT;
|
|
|
|
result = XFS_CMP_EXACT;
|
|
for (i = 0; i < len; i++) {
|
|
if (args->name[i] == name[i])
|
|
continue;
|
|
if (tolower(args->name[i]) != tolower(name[i]))
|
|
return XFS_CMP_DIFFERENT;
|
|
result = XFS_CMP_CASE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static struct xfs_nameops xfs_ascii_ci_nameops = {
|
|
.hashname = xfs_ascii_ci_hashname,
|
|
.compname = xfs_ascii_ci_compname,
|
|
};
|
|
|
|
void
|
|
xfs_dir_mount(
|
|
xfs_mount_t *mp)
|
|
{
|
|
int nodehdr_size;
|
|
|
|
|
|
ASSERT(xfs_sb_version_hasdirv2(&mp->m_sb));
|
|
ASSERT((1 << (mp->m_sb.sb_blocklog + mp->m_sb.sb_dirblklog)) <=
|
|
XFS_MAX_BLOCKSIZE);
|
|
mp->m_dirblksize = 1 << (mp->m_sb.sb_blocklog + mp->m_sb.sb_dirblklog);
|
|
mp->m_dirblkfsbs = 1 << mp->m_sb.sb_dirblklog;
|
|
mp->m_dirdatablk = xfs_dir2_db_to_da(mp, XFS_DIR2_DATA_FIRSTDB(mp));
|
|
mp->m_dirleafblk = xfs_dir2_db_to_da(mp, XFS_DIR2_LEAF_FIRSTDB(mp));
|
|
mp->m_dirfreeblk = xfs_dir2_db_to_da(mp, XFS_DIR2_FREE_FIRSTDB(mp));
|
|
|
|
nodehdr_size = __xfs_da3_node_hdr_size(xfs_sb_version_hascrc(&mp->m_sb));
|
|
mp->m_attr_node_ents = (mp->m_sb.sb_blocksize - nodehdr_size) /
|
|
(uint)sizeof(xfs_da_node_entry_t);
|
|
mp->m_dir_node_ents = (mp->m_dirblksize - nodehdr_size) /
|
|
(uint)sizeof(xfs_da_node_entry_t);
|
|
|
|
mp->m_dir_magicpct = (mp->m_dirblksize * 37) / 100;
|
|
if (xfs_sb_version_hasasciici(&mp->m_sb))
|
|
mp->m_dirnameops = &xfs_ascii_ci_nameops;
|
|
else
|
|
mp->m_dirnameops = &xfs_default_nameops;
|
|
|
|
if (xfs_sb_version_hascrc(&mp->m_sb))
|
|
mp->m_dir_inode_ops = &xfs_dir3_ops;
|
|
else if (xfs_sb_version_hasftype(&mp->m_sb))
|
|
mp->m_dir_inode_ops = &xfs_dir2_ftype_ops;
|
|
else
|
|
mp->m_dir_inode_ops = &xfs_dir2_ops;
|
|
}
|
|
|
|
/*
|
|
* Return 1 if directory contains only "." and "..".
|
|
*/
|
|
int
|
|
xfs_dir_isempty(
|
|
xfs_inode_t *dp)
|
|
{
|
|
xfs_dir2_sf_hdr_t *sfp;
|
|
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
if (dp->i_d.di_size == 0) /* might happen during shutdown. */
|
|
return 1;
|
|
if (dp->i_d.di_size > XFS_IFORK_DSIZE(dp))
|
|
return 0;
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
return !sfp->count;
|
|
}
|
|
|
|
/*
|
|
* Validate a given inode number.
|
|
*/
|
|
int
|
|
xfs_dir_ino_validate(
|
|
xfs_mount_t *mp,
|
|
xfs_ino_t ino)
|
|
{
|
|
xfs_agblock_t agblkno;
|
|
xfs_agino_t agino;
|
|
xfs_agnumber_t agno;
|
|
int ino_ok;
|
|
int ioff;
|
|
|
|
agno = XFS_INO_TO_AGNO(mp, ino);
|
|
agblkno = XFS_INO_TO_AGBNO(mp, ino);
|
|
ioff = XFS_INO_TO_OFFSET(mp, ino);
|
|
agino = XFS_OFFBNO_TO_AGINO(mp, agblkno, ioff);
|
|
ino_ok =
|
|
agno < mp->m_sb.sb_agcount &&
|
|
agblkno < mp->m_sb.sb_agblocks &&
|
|
agblkno != 0 &&
|
|
ioff < (1 << mp->m_sb.sb_inopblog) &&
|
|
XFS_AGINO_TO_INO(mp, agno, agino) == ino;
|
|
if (unlikely(XFS_TEST_ERROR(!ino_ok, mp, XFS_ERRTAG_DIR_INO_VALIDATE,
|
|
XFS_RANDOM_DIR_INO_VALIDATE))) {
|
|
xfs_warn(mp, "Invalid inode number 0x%Lx",
|
|
(unsigned long long) ino);
|
|
XFS_ERROR_REPORT("xfs_dir_ino_validate", XFS_ERRLEVEL_LOW, mp);
|
|
return XFS_ERROR(EFSCORRUPTED);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize a directory with its "." and ".." entries.
|
|
*/
|
|
int
|
|
xfs_dir_init(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
xfs_inode_t *pdp)
|
|
{
|
|
xfs_da_args_t args;
|
|
int error;
|
|
|
|
memset((char *)&args, 0, sizeof(args));
|
|
args.dp = dp;
|
|
args.trans = tp;
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
if ((error = xfs_dir_ino_validate(tp->t_mountp, pdp->i_ino)))
|
|
return error;
|
|
return xfs_dir2_sf_create(&args, pdp->i_ino);
|
|
}
|
|
|
|
/*
|
|
Enter a name in a directory.
|
|
*/
|
|
int
|
|
xfs_dir_createname(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
struct xfs_name *name,
|
|
xfs_ino_t inum, /* new entry inode number */
|
|
xfs_fsblock_t *first, /* bmap's firstblock */
|
|
xfs_bmap_free_t *flist, /* bmap's freeblock list */
|
|
xfs_extlen_t total) /* bmap's total block count */
|
|
{
|
|
xfs_da_args_t args;
|
|
int rval;
|
|
int v; /* type-checking value */
|
|
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
if ((rval = xfs_dir_ino_validate(tp->t_mountp, inum)))
|
|
return rval;
|
|
XFS_STATS_INC(xs_dir_create);
|
|
|
|
memset(&args, 0, sizeof(xfs_da_args_t));
|
|
args.name = name->name;
|
|
args.namelen = name->len;
|
|
args.filetype = name->type;
|
|
args.hashval = dp->i_mount->m_dirnameops->hashname(name);
|
|
args.inumber = inum;
|
|
args.dp = dp;
|
|
args.firstblock = first;
|
|
args.flist = flist;
|
|
args.total = total;
|
|
args.whichfork = XFS_DATA_FORK;
|
|
args.trans = tp;
|
|
args.op_flags = XFS_DA_OP_ADDNAME | XFS_DA_OP_OKNOENT;
|
|
|
|
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
|
|
rval = xfs_dir2_sf_addname(&args);
|
|
else if ((rval = xfs_dir2_isblock(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_block_addname(&args);
|
|
else if ((rval = xfs_dir2_isleaf(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_leaf_addname(&args);
|
|
else
|
|
rval = xfs_dir2_node_addname(&args);
|
|
return rval;
|
|
}
|
|
|
|
/*
|
|
* If doing a CI lookup and case-insensitive match, dup actual name into
|
|
* args.value. Return EEXIST for success (ie. name found) or an error.
|
|
*/
|
|
int
|
|
xfs_dir_cilookup_result(
|
|
struct xfs_da_args *args,
|
|
const unsigned char *name,
|
|
int len)
|
|
{
|
|
if (args->cmpresult == XFS_CMP_DIFFERENT)
|
|
return ENOENT;
|
|
if (args->cmpresult != XFS_CMP_CASE ||
|
|
!(args->op_flags & XFS_DA_OP_CILOOKUP))
|
|
return EEXIST;
|
|
|
|
args->value = kmem_alloc(len, KM_NOFS | KM_MAYFAIL);
|
|
if (!args->value)
|
|
return ENOMEM;
|
|
|
|
memcpy(args->value, name, len);
|
|
args->valuelen = len;
|
|
return EEXIST;
|
|
}
|
|
|
|
/*
|
|
* Lookup a name in a directory, give back the inode number.
|
|
* If ci_name is not NULL, returns the actual name in ci_name if it differs
|
|
* to name, or ci_name->name is set to NULL for an exact match.
|
|
*/
|
|
|
|
int
|
|
xfs_dir_lookup(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
struct xfs_name *name,
|
|
xfs_ino_t *inum, /* out: inode number */
|
|
struct xfs_name *ci_name) /* out: actual name if CI match */
|
|
{
|
|
xfs_da_args_t args;
|
|
int rval;
|
|
int v; /* type-checking value */
|
|
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
XFS_STATS_INC(xs_dir_lookup);
|
|
|
|
memset(&args, 0, sizeof(xfs_da_args_t));
|
|
args.name = name->name;
|
|
args.namelen = name->len;
|
|
args.filetype = name->type;
|
|
args.hashval = dp->i_mount->m_dirnameops->hashname(name);
|
|
args.dp = dp;
|
|
args.whichfork = XFS_DATA_FORK;
|
|
args.trans = tp;
|
|
args.op_flags = XFS_DA_OP_OKNOENT;
|
|
if (ci_name)
|
|
args.op_flags |= XFS_DA_OP_CILOOKUP;
|
|
|
|
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
|
|
rval = xfs_dir2_sf_lookup(&args);
|
|
else if ((rval = xfs_dir2_isblock(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_block_lookup(&args);
|
|
else if ((rval = xfs_dir2_isleaf(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_leaf_lookup(&args);
|
|
else
|
|
rval = xfs_dir2_node_lookup(&args);
|
|
if (rval == EEXIST)
|
|
rval = 0;
|
|
if (!rval) {
|
|
*inum = args.inumber;
|
|
if (ci_name) {
|
|
ci_name->name = args.value;
|
|
ci_name->len = args.valuelen;
|
|
}
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
/*
|
|
* Remove an entry from a directory.
|
|
*/
|
|
int
|
|
xfs_dir_removename(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
struct xfs_name *name,
|
|
xfs_ino_t ino,
|
|
xfs_fsblock_t *first, /* bmap's firstblock */
|
|
xfs_bmap_free_t *flist, /* bmap's freeblock list */
|
|
xfs_extlen_t total) /* bmap's total block count */
|
|
{
|
|
xfs_da_args_t args;
|
|
int rval;
|
|
int v; /* type-checking value */
|
|
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
XFS_STATS_INC(xs_dir_remove);
|
|
|
|
memset(&args, 0, sizeof(xfs_da_args_t));
|
|
args.name = name->name;
|
|
args.namelen = name->len;
|
|
args.filetype = name->type;
|
|
args.hashval = dp->i_mount->m_dirnameops->hashname(name);
|
|
args.inumber = ino;
|
|
args.dp = dp;
|
|
args.firstblock = first;
|
|
args.flist = flist;
|
|
args.total = total;
|
|
args.whichfork = XFS_DATA_FORK;
|
|
args.trans = tp;
|
|
|
|
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
|
|
rval = xfs_dir2_sf_removename(&args);
|
|
else if ((rval = xfs_dir2_isblock(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_block_removename(&args);
|
|
else if ((rval = xfs_dir2_isleaf(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_leaf_removename(&args);
|
|
else
|
|
rval = xfs_dir2_node_removename(&args);
|
|
return rval;
|
|
}
|
|
|
|
/*
|
|
* Replace the inode number of a directory entry.
|
|
*/
|
|
int
|
|
xfs_dir_replace(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
struct xfs_name *name, /* name of entry to replace */
|
|
xfs_ino_t inum, /* new inode number */
|
|
xfs_fsblock_t *first, /* bmap's firstblock */
|
|
xfs_bmap_free_t *flist, /* bmap's freeblock list */
|
|
xfs_extlen_t total) /* bmap's total block count */
|
|
{
|
|
xfs_da_args_t args;
|
|
int rval;
|
|
int v; /* type-checking value */
|
|
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
|
|
if ((rval = xfs_dir_ino_validate(tp->t_mountp, inum)))
|
|
return rval;
|
|
|
|
memset(&args, 0, sizeof(xfs_da_args_t));
|
|
args.name = name->name;
|
|
args.namelen = name->len;
|
|
args.filetype = name->type;
|
|
args.hashval = dp->i_mount->m_dirnameops->hashname(name);
|
|
args.inumber = inum;
|
|
args.dp = dp;
|
|
args.firstblock = first;
|
|
args.flist = flist;
|
|
args.total = total;
|
|
args.whichfork = XFS_DATA_FORK;
|
|
args.trans = tp;
|
|
|
|
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
|
|
rval = xfs_dir2_sf_replace(&args);
|
|
else if ((rval = xfs_dir2_isblock(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_block_replace(&args);
|
|
else if ((rval = xfs_dir2_isleaf(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_leaf_replace(&args);
|
|
else
|
|
rval = xfs_dir2_node_replace(&args);
|
|
return rval;
|
|
}
|
|
|
|
/*
|
|
* See if this entry can be added to the directory without allocating space.
|
|
* First checks that the caller couldn't reserve enough space (resblks = 0).
|
|
*/
|
|
int
|
|
xfs_dir_canenter(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
struct xfs_name *name, /* name of entry to add */
|
|
uint resblks)
|
|
{
|
|
xfs_da_args_t args;
|
|
int rval;
|
|
int v; /* type-checking value */
|
|
|
|
if (resblks)
|
|
return 0;
|
|
|
|
ASSERT(S_ISDIR(dp->i_d.di_mode));
|
|
|
|
memset(&args, 0, sizeof(xfs_da_args_t));
|
|
args.name = name->name;
|
|
args.namelen = name->len;
|
|
args.filetype = name->type;
|
|
args.hashval = dp->i_mount->m_dirnameops->hashname(name);
|
|
args.dp = dp;
|
|
args.whichfork = XFS_DATA_FORK;
|
|
args.trans = tp;
|
|
args.op_flags = XFS_DA_OP_JUSTCHECK | XFS_DA_OP_ADDNAME |
|
|
XFS_DA_OP_OKNOENT;
|
|
|
|
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
|
|
rval = xfs_dir2_sf_addname(&args);
|
|
else if ((rval = xfs_dir2_isblock(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_block_addname(&args);
|
|
else if ((rval = xfs_dir2_isleaf(tp, dp, &v)))
|
|
return rval;
|
|
else if (v)
|
|
rval = xfs_dir2_leaf_addname(&args);
|
|
else
|
|
rval = xfs_dir2_node_addname(&args);
|
|
return rval;
|
|
}
|
|
|
|
/*
|
|
* Utility routines.
|
|
*/
|
|
|
|
/*
|
|
* Add a block to the directory.
|
|
*
|
|
* This routine is for data and free blocks, not leaf/node blocks which are
|
|
* handled by xfs_da_grow_inode.
|
|
*/
|
|
int
|
|
xfs_dir2_grow_inode(
|
|
struct xfs_da_args *args,
|
|
int space, /* v2 dir's space XFS_DIR2_xxx_SPACE */
|
|
xfs_dir2_db_t *dbp) /* out: block number added */
|
|
{
|
|
struct xfs_inode *dp = args->dp;
|
|
struct xfs_mount *mp = dp->i_mount;
|
|
xfs_fileoff_t bno; /* directory offset of new block */
|
|
int count; /* count of filesystem blocks */
|
|
int error;
|
|
|
|
trace_xfs_dir2_grow_inode(args, space);
|
|
|
|
/*
|
|
* Set lowest possible block in the space requested.
|
|
*/
|
|
bno = XFS_B_TO_FSBT(mp, space * XFS_DIR2_SPACE_SIZE);
|
|
count = mp->m_dirblkfsbs;
|
|
|
|
error = xfs_da_grow_inode_int(args, &bno, count);
|
|
if (error)
|
|
return error;
|
|
|
|
*dbp = xfs_dir2_da_to_db(mp, (xfs_dablk_t)bno);
|
|
|
|
/*
|
|
* Update file's size if this is the data space and it grew.
|
|
*/
|
|
if (space == XFS_DIR2_DATA_SPACE) {
|
|
xfs_fsize_t size; /* directory file (data) size */
|
|
|
|
size = XFS_FSB_TO_B(mp, bno + count);
|
|
if (size > dp->i_d.di_size) {
|
|
dp->i_d.di_size = size;
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* See if the directory is a single-block form directory.
|
|
*/
|
|
int
|
|
xfs_dir2_isblock(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
int *vp) /* out: 1 is block, 0 is not block */
|
|
{
|
|
xfs_fileoff_t last; /* last file offset */
|
|
xfs_mount_t *mp;
|
|
int rval;
|
|
|
|
mp = dp->i_mount;
|
|
if ((rval = xfs_bmap_last_offset(tp, dp, &last, XFS_DATA_FORK)))
|
|
return rval;
|
|
rval = XFS_FSB_TO_B(mp, last) == mp->m_dirblksize;
|
|
ASSERT(rval == 0 || dp->i_d.di_size == mp->m_dirblksize);
|
|
*vp = rval;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* See if the directory is a single-leaf form directory.
|
|
*/
|
|
int
|
|
xfs_dir2_isleaf(
|
|
xfs_trans_t *tp,
|
|
xfs_inode_t *dp,
|
|
int *vp) /* out: 1 is leaf, 0 is not leaf */
|
|
{
|
|
xfs_fileoff_t last; /* last file offset */
|
|
xfs_mount_t *mp;
|
|
int rval;
|
|
|
|
mp = dp->i_mount;
|
|
if ((rval = xfs_bmap_last_offset(tp, dp, &last, XFS_DATA_FORK)))
|
|
return rval;
|
|
*vp = last == mp->m_dirleafblk + (1 << mp->m_sb.sb_dirblklog);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Remove the given block from the directory.
|
|
* This routine is used for data and free blocks, leaf/node are done
|
|
* by xfs_da_shrink_inode.
|
|
*/
|
|
int
|
|
xfs_dir2_shrink_inode(
|
|
xfs_da_args_t *args,
|
|
xfs_dir2_db_t db,
|
|
struct xfs_buf *bp)
|
|
{
|
|
xfs_fileoff_t bno; /* directory file offset */
|
|
xfs_dablk_t da; /* directory file offset */
|
|
int done; /* bunmap is finished */
|
|
xfs_inode_t *dp;
|
|
int error;
|
|
xfs_mount_t *mp;
|
|
xfs_trans_t *tp;
|
|
|
|
trace_xfs_dir2_shrink_inode(args, db);
|
|
|
|
dp = args->dp;
|
|
mp = dp->i_mount;
|
|
tp = args->trans;
|
|
da = xfs_dir2_db_to_da(mp, db);
|
|
/*
|
|
* Unmap the fsblock(s).
|
|
*/
|
|
if ((error = xfs_bunmapi(tp, dp, da, mp->m_dirblkfsbs,
|
|
XFS_BMAPI_METADATA, 0, args->firstblock, args->flist,
|
|
&done))) {
|
|
/*
|
|
* ENOSPC actually can happen if we're in a removename with
|
|
* no space reservation, and the resulting block removal
|
|
* would cause a bmap btree split or conversion from extents
|
|
* to btree. This can only happen for un-fragmented
|
|
* directory blocks, since you need to be punching out
|
|
* the middle of an extent.
|
|
* In this case we need to leave the block in the file,
|
|
* and not binval it.
|
|
* So the block has to be in a consistent empty state
|
|
* and appropriately logged.
|
|
* We don't free up the buffer, the caller can tell it
|
|
* hasn't happened since it got an error back.
|
|
*/
|
|
return error;
|
|
}
|
|
ASSERT(done);
|
|
/*
|
|
* Invalidate the buffer from the transaction.
|
|
*/
|
|
xfs_trans_binval(tp, bp);
|
|
/*
|
|
* If it's not a data block, we're done.
|
|
*/
|
|
if (db >= XFS_DIR2_LEAF_FIRSTDB(mp))
|
|
return 0;
|
|
/*
|
|
* If the block isn't the last one in the directory, we're done.
|
|
*/
|
|
if (dp->i_d.di_size > xfs_dir2_db_off_to_byte(mp, db + 1, 0))
|
|
return 0;
|
|
bno = da;
|
|
if ((error = xfs_bmap_last_before(tp, dp, &bno, XFS_DATA_FORK))) {
|
|
/*
|
|
* This can't really happen unless there's kernel corruption.
|
|
*/
|
|
return error;
|
|
}
|
|
if (db == mp->m_dirdatablk)
|
|
ASSERT(bno == 0);
|
|
else
|
|
ASSERT(bno > 0);
|
|
/*
|
|
* Set the size to the new last block.
|
|
*/
|
|
dp->i_d.di_size = XFS_FSB_TO_B(mp, bno);
|
|
xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
|
|
return 0;
|
|
}
|