alistair23-linux/fs/xfs/xfs_dir.c
Nathan Scott d8cc890d40 [XFS] Ondisk format extension for extended attributes (attr2). Basically,
the data/attr forks now grow up/down from either end of the literal area,
rather than dividing the literal area into two chunks and growing both
upward.  Means we can now make much more efficient use of the attribute
space, incl. fitting DMF attributes inline in 256 byte inodes, and large
jumps in dbench3 performance numbers.  It is self enabling, but can be
forced on/off via the attr2/noattr2 mount options.

SGI-PV: 941645
SGI-Modid: xfs-linux:xfs-kern:23835a

Signed-off-by: Nathan Scott <nathans@sgi.com>
2005-11-02 10:34:53 +11:00

1236 lines
33 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 version 2 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.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
* Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
* Mountain View, CA 94043, or:
*
* http://www.sgi.com
*
* For further information regarding this notice, see:
*
* http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/
*/
#include "xfs.h"
#include "xfs_macros.h"
#include "xfs_types.h"
#include "xfs_inum.h"
#include "xfs_log.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_dir.h"
#include "xfs_dir2.h"
#include "xfs_dmapi.h"
#include "xfs_mount.h"
#include "xfs_alloc_btree.h"
#include "xfs_bmap_btree.h"
#include "xfs_ialloc_btree.h"
#include "xfs_alloc.h"
#include "xfs_btree.h"
#include "xfs_attr_sf.h"
#include "xfs_dir_sf.h"
#include "xfs_dir2_sf.h"
#include "xfs_dinode.h"
#include "xfs_inode.h"
#include "xfs_bmap.h"
#include "xfs_da_btree.h"
#include "xfs_dir_leaf.h"
#include "xfs_error.h"
/*
* xfs_dir.c
*
* Provide the external interfaces to manage directories.
*/
/*========================================================================
* Function prototypes for the kernel.
*========================================================================*/
/*
* Functions for the dirops interfaces.
*/
static void xfs_dir_mount(struct xfs_mount *mp);
static int xfs_dir_isempty(struct xfs_inode *dp);
static int xfs_dir_init(struct xfs_trans *trans,
struct xfs_inode *dir,
struct xfs_inode *parent_dir);
static int xfs_dir_createname(struct xfs_trans *trans,
struct xfs_inode *dp,
char *name_string,
int name_len,
xfs_ino_t inode_number,
xfs_fsblock_t *firstblock,
xfs_bmap_free_t *flist,
xfs_extlen_t total);
static int xfs_dir_lookup(struct xfs_trans *tp,
struct xfs_inode *dp,
char *name_string,
int name_length,
xfs_ino_t *inode_number);
static int xfs_dir_removename(struct xfs_trans *trans,
struct xfs_inode *dp,
char *name_string,
int name_length,
xfs_ino_t ino,
xfs_fsblock_t *firstblock,
xfs_bmap_free_t *flist,
xfs_extlen_t total);
static int xfs_dir_getdents(struct xfs_trans *tp,
struct xfs_inode *dp,
struct uio *uiop,
int *eofp);
static int xfs_dir_replace(struct xfs_trans *tp,
struct xfs_inode *dp,
char *name_string,
int name_length,
xfs_ino_t inode_number,
xfs_fsblock_t *firstblock,
xfs_bmap_free_t *flist,
xfs_extlen_t total);
static int xfs_dir_canenter(struct xfs_trans *tp,
struct xfs_inode *dp,
char *name_string,
int name_length);
static int xfs_dir_shortform_validate_ondisk(xfs_mount_t *mp,
xfs_dinode_t *dip);
xfs_dirops_t xfsv1_dirops = {
.xd_mount = xfs_dir_mount,
.xd_isempty = xfs_dir_isempty,
.xd_init = xfs_dir_init,
.xd_createname = xfs_dir_createname,
.xd_lookup = xfs_dir_lookup,
.xd_removename = xfs_dir_removename,
.xd_getdents = xfs_dir_getdents,
.xd_replace = xfs_dir_replace,
.xd_canenter = xfs_dir_canenter,
.xd_shortform_validate_ondisk = xfs_dir_shortform_validate_ondisk,
.xd_shortform_to_single = xfs_dir_shortform_to_leaf,
};
/*
* Internal routines when dirsize == XFS_LBSIZE(mp).
*/
STATIC int xfs_dir_leaf_lookup(xfs_da_args_t *args);
STATIC int xfs_dir_leaf_removename(xfs_da_args_t *args, int *number_entries,
int *total_namebytes);
STATIC int xfs_dir_leaf_getdents(xfs_trans_t *trans, xfs_inode_t *dp,
uio_t *uio, int *eofp,
xfs_dirent_t *dbp,
xfs_dir_put_t put);
STATIC int xfs_dir_leaf_replace(xfs_da_args_t *args);
/*
* Internal routines when dirsize > XFS_LBSIZE(mp).
*/
STATIC int xfs_dir_node_addname(xfs_da_args_t *args);
STATIC int xfs_dir_node_lookup(xfs_da_args_t *args);
STATIC int xfs_dir_node_removename(xfs_da_args_t *args);
STATIC int xfs_dir_node_getdents(xfs_trans_t *trans, xfs_inode_t *dp,
uio_t *uio, int *eofp,
xfs_dirent_t *dbp,
xfs_dir_put_t put);
STATIC int xfs_dir_node_replace(xfs_da_args_t *args);
#if defined(XFS_DIR_TRACE)
ktrace_t *xfs_dir_trace_buf;
#endif
/*========================================================================
* Overall external interface routines.
*========================================================================*/
xfs_dahash_t xfs_dir_hash_dot, xfs_dir_hash_dotdot;
/*
* One-time startup routine called from xfs_init().
*/
void
xfs_dir_startup(void)
{
xfs_dir_hash_dot = xfs_da_hashname(".", 1);
xfs_dir_hash_dotdot = xfs_da_hashname("..", 2);
}
/*
* Initialize directory-related fields in the mount structure.
*/
static void
xfs_dir_mount(xfs_mount_t *mp)
{
uint shortcount, leafcount, count;
mp->m_dirversion = 1;
if (mp->m_flags & XFS_MOUNT_COMPAT_ATTR) {
shortcount = (mp->m_attroffset -
(uint)sizeof(xfs_dir_sf_hdr_t)) /
(uint)sizeof(xfs_dir_sf_entry_t);
leafcount = (XFS_LBSIZE(mp) -
(uint)sizeof(xfs_dir_leaf_hdr_t)) /
((uint)sizeof(xfs_dir_leaf_entry_t) +
(uint)sizeof(xfs_dir_leaf_name_t));
} else {
shortcount = (XFS_BMDR_SPACE_CALC(MINABTPTRS) -
(uint)sizeof(xfs_dir_sf_hdr_t)) /
(uint)sizeof(xfs_dir_sf_entry_t);
leafcount = (XFS_LBSIZE(mp) -
(uint)sizeof(xfs_dir_leaf_hdr_t)) /
((uint)sizeof(xfs_dir_leaf_entry_t) +
(uint)sizeof(xfs_dir_leaf_name_t));
}
count = shortcount > leafcount ? shortcount : leafcount;
mp->m_dircook_elog = xfs_da_log2_roundup(count + 1);
ASSERT(mp->m_dircook_elog <= mp->m_sb.sb_blocklog);
mp->m_dir_node_ents = mp->m_attr_node_ents =
(XFS_LBSIZE(mp) - (uint)sizeof(xfs_da_node_hdr_t)) /
(uint)sizeof(xfs_da_node_entry_t);
mp->m_dir_magicpct = (XFS_LBSIZE(mp) * 37) / 100;
mp->m_dirblksize = mp->m_sb.sb_blocksize;
mp->m_dirblkfsbs = 1;
}
/*
* Return 1 if directory contains only "." and "..".
*/
static int
xfs_dir_isempty(xfs_inode_t *dp)
{
xfs_dir_sf_hdr_t *hdr;
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
if (dp->i_d.di_size == 0)
return(1);
if (dp->i_d.di_size > XFS_IFORK_DSIZE(dp))
return(0);
hdr = (xfs_dir_sf_hdr_t *)dp->i_df.if_u1.if_data;
return(hdr->count == 0);
}
/*
* Initialize a directory with its "." and ".." entries.
*/
static int
xfs_dir_init(xfs_trans_t *trans, xfs_inode_t *dir, xfs_inode_t *parent_dir)
{
xfs_da_args_t args;
int error;
memset((char *)&args, 0, sizeof(args));
args.dp = dir;
args.trans = trans;
ASSERT((dir->i_d.di_mode & S_IFMT) == S_IFDIR);
if ((error = xfs_dir_ino_validate(trans->t_mountp, parent_dir->i_ino)))
return error;
return(xfs_dir_shortform_create(&args, parent_dir->i_ino));
}
/*
* Generic handler routine to add a name to a directory.
* Transitions directory from shortform to Btree as necessary.
*/
static int /* error */
xfs_dir_createname(xfs_trans_t *trans, xfs_inode_t *dp, char *name,
int namelen, xfs_ino_t inum, xfs_fsblock_t *firstblock,
xfs_bmap_free_t *flist, xfs_extlen_t total)
{
xfs_da_args_t args;
int retval, newsize, done;
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
if ((retval = xfs_dir_ino_validate(trans->t_mountp, inum)))
return (retval);
XFS_STATS_INC(xs_dir_create);
/*
* Fill in the arg structure for this request.
*/
args.name = name;
args.namelen = namelen;
args.hashval = xfs_da_hashname(name, namelen);
args.inumber = inum;
args.dp = dp;
args.firstblock = firstblock;
args.flist = flist;
args.total = total;
args.whichfork = XFS_DATA_FORK;
args.trans = trans;
args.justcheck = 0;
args.addname = args.oknoent = 1;
/*
* Decide on what work routines to call based on the inode size.
*/
done = 0;
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
newsize = XFS_DIR_SF_ENTSIZE_BYNAME(args.namelen);
if ((dp->i_d.di_size + newsize) <= XFS_IFORK_DSIZE(dp)) {
retval = xfs_dir_shortform_addname(&args);
done = 1;
} else {
if (total == 0)
return XFS_ERROR(ENOSPC);
retval = xfs_dir_shortform_to_leaf(&args);
done = retval != 0;
}
}
if (!done && xfs_bmap_one_block(dp, XFS_DATA_FORK)) {
retval = xfs_dir_leaf_addname(&args);
done = retval != ENOSPC;
if (!done) {
if (total == 0)
return XFS_ERROR(ENOSPC);
retval = xfs_dir_leaf_to_node(&args);
done = retval != 0;
}
}
if (!done) {
retval = xfs_dir_node_addname(&args);
}
return(retval);
}
/*
* Generic handler routine to check if a name can be added to a directory,
* without adding any blocks to the directory.
*/
static int /* error */
xfs_dir_canenter(xfs_trans_t *trans, xfs_inode_t *dp, char *name, int namelen)
{
xfs_da_args_t args;
int retval, newsize;
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
/*
* Fill in the arg structure for this request.
*/
args.name = name;
args.namelen = namelen;
args.hashval = xfs_da_hashname(name, namelen);
args.inumber = 0;
args.dp = dp;
args.firstblock = NULL;
args.flist = NULL;
args.total = 0;
args.whichfork = XFS_DATA_FORK;
args.trans = trans;
args.justcheck = args.addname = args.oknoent = 1;
/*
* Decide on what work routines to call based on the inode size.
*/
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
newsize = XFS_DIR_SF_ENTSIZE_BYNAME(args.namelen);
if ((dp->i_d.di_size + newsize) <= XFS_IFORK_DSIZE(dp))
retval = 0;
else
retval = XFS_ERROR(ENOSPC);
} else if (xfs_bmap_one_block(dp, XFS_DATA_FORK)) {
retval = xfs_dir_leaf_addname(&args);
} else {
retval = xfs_dir_node_addname(&args);
}
return(retval);
}
/*
* Generic handler routine to remove a name from a directory.
* Transitions directory from Btree to shortform as necessary.
*/
static int /* error */
xfs_dir_removename(xfs_trans_t *trans, xfs_inode_t *dp, char *name,
int namelen, xfs_ino_t ino, xfs_fsblock_t *firstblock,
xfs_bmap_free_t *flist, xfs_extlen_t total)
{
xfs_da_args_t args;
int count, totallen, newsize, retval;
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
XFS_STATS_INC(xs_dir_remove);
/*
* Fill in the arg structure for this request.
*/
args.name = name;
args.namelen = namelen;
args.hashval = xfs_da_hashname(name, namelen);
args.inumber = ino;
args.dp = dp;
args.firstblock = firstblock;
args.flist = flist;
args.total = total;
args.whichfork = XFS_DATA_FORK;
args.trans = trans;
args.justcheck = args.addname = args.oknoent = 0;
/*
* Decide on what work routines to call based on the inode size.
*/
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
retval = xfs_dir_shortform_removename(&args);
} else if (xfs_bmap_one_block(dp, XFS_DATA_FORK)) {
retval = xfs_dir_leaf_removename(&args, &count, &totallen);
if (retval == 0) {
newsize = XFS_DIR_SF_ALLFIT(count, totallen);
if (newsize <= XFS_IFORK_DSIZE(dp)) {
retval = xfs_dir_leaf_to_shortform(&args);
}
}
} else {
retval = xfs_dir_node_removename(&args);
}
return(retval);
}
static int /* error */
xfs_dir_lookup(xfs_trans_t *trans, xfs_inode_t *dp, char *name, int namelen,
xfs_ino_t *inum)
{
xfs_da_args_t args;
int retval;
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
XFS_STATS_INC(xs_dir_lookup);
/*
* Fill in the arg structure for this request.
*/
args.name = name;
args.namelen = namelen;
args.hashval = xfs_da_hashname(name, namelen);
args.inumber = 0;
args.dp = dp;
args.firstblock = NULL;
args.flist = NULL;
args.total = 0;
args.whichfork = XFS_DATA_FORK;
args.trans = trans;
args.justcheck = args.addname = 0;
args.oknoent = 1;
/*
* Decide on what work routines to call based on the inode size.
*/
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
retval = xfs_dir_shortform_lookup(&args);
} else if (xfs_bmap_one_block(dp, XFS_DATA_FORK)) {
retval = xfs_dir_leaf_lookup(&args);
} else {
retval = xfs_dir_node_lookup(&args);
}
if (retval == EEXIST)
retval = 0;
*inum = args.inumber;
return(retval);
}
/*
* Implement readdir.
*/
static int /* error */
xfs_dir_getdents(xfs_trans_t *trans, xfs_inode_t *dp, uio_t *uio, int *eofp)
{
xfs_dirent_t *dbp;
int alignment, retval;
xfs_dir_put_t put;
XFS_STATS_INC(xs_dir_getdents);
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
/*
* If our caller has given us a single contiguous memory buffer,
* just work directly within that buffer. If it's in user memory,
* lock it down first.
*/
alignment = sizeof(xfs_off_t) - 1;
if ((uio->uio_iovcnt == 1) &&
(((__psint_t)uio->uio_iov[0].iov_base & alignment) == 0) &&
((uio->uio_iov[0].iov_len & alignment) == 0)) {
dbp = NULL;
put = xfs_dir_put_dirent64_direct;
} else {
dbp = kmem_alloc(sizeof(*dbp) + MAXNAMELEN, KM_SLEEP);
put = xfs_dir_put_dirent64_uio;
}
/*
* Decide on what work routines to call based on the inode size.
*/
*eofp = 0;
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
retval = xfs_dir_shortform_getdents(dp, uio, eofp, dbp, put);
} else if (xfs_bmap_one_block(dp, XFS_DATA_FORK)) {
retval = xfs_dir_leaf_getdents(trans, dp, uio, eofp, dbp, put);
} else {
retval = xfs_dir_node_getdents(trans, dp, uio, eofp, dbp, put);
}
if (dbp != NULL)
kmem_free(dbp, sizeof(*dbp) + MAXNAMELEN);
return(retval);
}
static int /* error */
xfs_dir_replace(xfs_trans_t *trans, xfs_inode_t *dp, char *name, int namelen,
xfs_ino_t inum, xfs_fsblock_t *firstblock,
xfs_bmap_free_t *flist, xfs_extlen_t total)
{
xfs_da_args_t args;
int retval;
ASSERT((dp->i_d.di_mode & S_IFMT) == S_IFDIR);
if ((retval = xfs_dir_ino_validate(trans->t_mountp, inum)))
return retval;
/*
* Fill in the arg structure for this request.
*/
args.name = name;
args.namelen = namelen;
args.hashval = xfs_da_hashname(name, namelen);
args.inumber = inum;
args.dp = dp;
args.firstblock = firstblock;
args.flist = flist;
args.total = total;
args.whichfork = XFS_DATA_FORK;
args.trans = trans;
args.justcheck = args.addname = args.oknoent = 0;
/*
* Decide on what work routines to call based on the inode size.
*/
if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
retval = xfs_dir_shortform_replace(&args);
} else if (xfs_bmap_one_block(dp, XFS_DATA_FORK)) {
retval = xfs_dir_leaf_replace(&args);
} else {
retval = xfs_dir_node_replace(&args);
}
return(retval);
}
static int
xfs_dir_shortform_validate_ondisk(xfs_mount_t *mp, xfs_dinode_t *dp)
{
xfs_ino_t ino;
int namelen_sum;
int count;
xfs_dir_shortform_t *sf;
xfs_dir_sf_entry_t *sfe;
int i;
if ((INT_GET(dp->di_core.di_mode, ARCH_CONVERT) & S_IFMT) != S_IFDIR) {
return 0;
}
if (INT_GET(dp->di_core.di_format, ARCH_CONVERT) != XFS_DINODE_FMT_LOCAL) {
return 0;
}
if (INT_GET(dp->di_core.di_size, ARCH_CONVERT) < sizeof(sf->hdr)) {
xfs_fs_cmn_err(CE_WARN, mp, "Invalid shortform size: dp 0x%p",
dp);
return 1;
}
sf = (xfs_dir_shortform_t *)(&dp->di_u.di_dirsf);
ino = XFS_GET_DIR_INO8(sf->hdr.parent);
if (xfs_dir_ino_validate(mp, ino))
return 1;
count = sf->hdr.count;
if ((count < 0) || ((count * 10) > XFS_LITINO(mp))) {
xfs_fs_cmn_err(CE_WARN, mp,
"Invalid shortform count: dp 0x%p", dp);
return(1);
}
if (count == 0) {
return 0;
}
namelen_sum = 0;
sfe = &sf->list[0];
for (i = sf->hdr.count - 1; i >= 0; i--) {
ino = XFS_GET_DIR_INO8(sfe->inumber);
xfs_dir_ino_validate(mp, ino);
if (sfe->namelen >= XFS_LITINO(mp)) {
xfs_fs_cmn_err(CE_WARN, mp,
"Invalid shortform namelen: dp 0x%p", dp);
return 1;
}
namelen_sum += sfe->namelen;
sfe = XFS_DIR_SF_NEXTENTRY(sfe);
}
if (namelen_sum >= XFS_LITINO(mp)) {
xfs_fs_cmn_err(CE_WARN, mp,
"Invalid shortform namelen: dp 0x%p", dp);
return 1;
}
return 0;
}
/*========================================================================
* External routines when dirsize == XFS_LBSIZE(dp->i_mount).
*========================================================================*/
/*
* Add a name to the leaf directory structure
* This is the external routine.
*/
int
xfs_dir_leaf_addname(xfs_da_args_t *args)
{
int index, retval;
xfs_dabuf_t *bp;
retval = xfs_da_read_buf(args->trans, args->dp, 0, -1, &bp,
XFS_DATA_FORK);
if (retval)
return(retval);
ASSERT(bp != NULL);
retval = xfs_dir_leaf_lookup_int(bp, args, &index);
if (retval == ENOENT)
retval = xfs_dir_leaf_add(bp, args, index);
xfs_da_buf_done(bp);
return(retval);
}
/*
* Remove a name from the leaf directory structure
* This is the external routine.
*/
STATIC int
xfs_dir_leaf_removename(xfs_da_args_t *args, int *count, int *totallen)
{
xfs_dir_leafblock_t *leaf;
int index, retval;
xfs_dabuf_t *bp;
retval = xfs_da_read_buf(args->trans, args->dp, 0, -1, &bp,
XFS_DATA_FORK);
if (retval)
return(retval);
ASSERT(bp != NULL);
leaf = bp->data;
ASSERT(INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) == XFS_DIR_LEAF_MAGIC);
retval = xfs_dir_leaf_lookup_int(bp, args, &index);
if (retval == EEXIST) {
(void)xfs_dir_leaf_remove(args->trans, bp, index);
*count = INT_GET(leaf->hdr.count, ARCH_CONVERT);
*totallen = INT_GET(leaf->hdr.namebytes, ARCH_CONVERT);
retval = 0;
}
xfs_da_buf_done(bp);
return(retval);
}
/*
* Look up a name in a leaf directory structure.
* This is the external routine.
*/
STATIC int
xfs_dir_leaf_lookup(xfs_da_args_t *args)
{
int index, retval;
xfs_dabuf_t *bp;
retval = xfs_da_read_buf(args->trans, args->dp, 0, -1, &bp,
XFS_DATA_FORK);
if (retval)
return(retval);
ASSERT(bp != NULL);
retval = xfs_dir_leaf_lookup_int(bp, args, &index);
xfs_da_brelse(args->trans, bp);
return(retval);
}
/*
* Copy out directory entries for getdents(), for leaf directories.
*/
STATIC int
xfs_dir_leaf_getdents(xfs_trans_t *trans, xfs_inode_t *dp, uio_t *uio,
int *eofp, xfs_dirent_t *dbp, xfs_dir_put_t put)
{
xfs_dabuf_t *bp;
int retval, eob;
retval = xfs_da_read_buf(dp->i_transp, dp, 0, -1, &bp, XFS_DATA_FORK);
if (retval)
return(retval);
ASSERT(bp != NULL);
retval = xfs_dir_leaf_getdents_int(bp, dp, 0, uio, &eob, dbp, put, -1);
xfs_da_brelse(trans, bp);
*eofp = (eob == 0);
return(retval);
}
/*
* Look up a name in a leaf directory structure, replace the inode number.
* This is the external routine.
*/
STATIC int
xfs_dir_leaf_replace(xfs_da_args_t *args)
{
int index, retval;
xfs_dabuf_t *bp;
xfs_ino_t inum;
xfs_dir_leafblock_t *leaf;
xfs_dir_leaf_entry_t *entry;
xfs_dir_leaf_name_t *namest;
inum = args->inumber;
retval = xfs_da_read_buf(args->trans, args->dp, 0, -1, &bp,
XFS_DATA_FORK);
if (retval)
return(retval);
ASSERT(bp != NULL);
retval = xfs_dir_leaf_lookup_int(bp, args, &index);
if (retval == EEXIST) {
leaf = bp->data;
entry = &leaf->entries[index];
namest = XFS_DIR_LEAF_NAMESTRUCT(leaf, INT_GET(entry->nameidx, ARCH_CONVERT));
/* XXX - replace assert? */
XFS_DIR_SF_PUT_DIRINO(&inum, &namest->inumber);
xfs_da_log_buf(args->trans, bp,
XFS_DA_LOGRANGE(leaf, namest, sizeof(namest->inumber)));
xfs_da_buf_done(bp);
retval = 0;
} else
xfs_da_brelse(args->trans, bp);
return(retval);
}
/*========================================================================
* External routines when dirsize > XFS_LBSIZE(mp).
*========================================================================*/
/*
* Add a name to a Btree-format directory.
*
* This will involve walking down the Btree, and may involve splitting
* leaf nodes and even splitting intermediate nodes up to and including
* the root node (a special case of an intermediate node).
*/
STATIC int
xfs_dir_node_addname(xfs_da_args_t *args)
{
xfs_da_state_t *state;
xfs_da_state_blk_t *blk;
int retval, error;
/*
* Fill in bucket of arguments/results/context to carry around.
*/
state = xfs_da_state_alloc();
state->args = args;
state->mp = args->dp->i_mount;
state->blocksize = state->mp->m_sb.sb_blocksize;
state->node_ents = state->mp->m_dir_node_ents;
/*
* Search to see if name already exists, and get back a pointer
* to where it should go.
*/
error = xfs_da_node_lookup_int(state, &retval);
if (error)
retval = error;
if (retval != ENOENT)
goto error;
blk = &state->path.blk[ state->path.active-1 ];
ASSERT(blk->magic == XFS_DIR_LEAF_MAGIC);
retval = xfs_dir_leaf_add(blk->bp, args, blk->index);
if (retval == 0) {
/*
* Addition succeeded, update Btree hashvals.
*/
if (!args->justcheck)
xfs_da_fixhashpath(state, &state->path);
} else {
/*
* Addition failed, split as many Btree elements as required.
*/
if (args->total == 0) {
ASSERT(retval == ENOSPC);
goto error;
}
retval = xfs_da_split(state);
}
error:
xfs_da_state_free(state);
return(retval);
}
/*
* Remove a name from a B-tree directory.
*
* This will involve walking down the Btree, and may involve joining
* leaf nodes and even joining intermediate nodes up to and including
* the root node (a special case of an intermediate node).
*/
STATIC int
xfs_dir_node_removename(xfs_da_args_t *args)
{
xfs_da_state_t *state;
xfs_da_state_blk_t *blk;
int retval, error;
state = xfs_da_state_alloc();
state->args = args;
state->mp = args->dp->i_mount;
state->blocksize = state->mp->m_sb.sb_blocksize;
state->node_ents = state->mp->m_dir_node_ents;
/*
* Search to see if name exists, and get back a pointer to it.
*/
error = xfs_da_node_lookup_int(state, &retval);
if (error)
retval = error;
if (retval != EEXIST) {
xfs_da_state_free(state);
return(retval);
}
/*
* Remove the name and update the hashvals in the tree.
*/
blk = &state->path.blk[ state->path.active-1 ];
ASSERT(blk->magic == XFS_DIR_LEAF_MAGIC);
retval = xfs_dir_leaf_remove(args->trans, blk->bp, blk->index);
xfs_da_fixhashpath(state, &state->path);
/*
* Check to see if the tree needs to be collapsed.
*/
error = 0;
if (retval) {
error = xfs_da_join(state);
}
xfs_da_state_free(state);
if (error)
return(error);
return(0);
}
/*
* Look up a filename in a int directory.
* Use an internal routine to actually do all the work.
*/
STATIC int
xfs_dir_node_lookup(xfs_da_args_t *args)
{
xfs_da_state_t *state;
int retval, error, i;
state = xfs_da_state_alloc();
state->args = args;
state->mp = args->dp->i_mount;
state->blocksize = state->mp->m_sb.sb_blocksize;
state->node_ents = state->mp->m_dir_node_ents;
/*
* Search to see if name exists,
* and get back a pointer to it.
*/
error = xfs_da_node_lookup_int(state, &retval);
if (error) {
retval = error;
}
/*
* If not in a transaction, we have to release all the buffers.
*/
for (i = 0; i < state->path.active; i++) {
xfs_da_brelse(args->trans, state->path.blk[i].bp);
state->path.blk[i].bp = NULL;
}
xfs_da_state_free(state);
return(retval);
}
STATIC int
xfs_dir_node_getdents(xfs_trans_t *trans, xfs_inode_t *dp, uio_t *uio,
int *eofp, xfs_dirent_t *dbp, xfs_dir_put_t put)
{
xfs_da_intnode_t *node;
xfs_da_node_entry_t *btree;
xfs_dir_leafblock_t *leaf = NULL;
xfs_dablk_t bno, nextbno;
xfs_dahash_t cookhash;
xfs_mount_t *mp;
int error, eob, i;
xfs_dabuf_t *bp;
xfs_daddr_t nextda;
/*
* Pick up our context.
*/
mp = dp->i_mount;
bp = NULL;
bno = XFS_DA_COOKIE_BNO(mp, uio->uio_offset);
cookhash = XFS_DA_COOKIE_HASH(mp, uio->uio_offset);
xfs_dir_trace_g_du("node: start", dp, uio);
/*
* Re-find our place, even if we're confused about what our place is.
*
* First we check the block number from the magic cookie, it is a
* cache of where we ended last time. If we find a leaf block, and
* the starting hashval in that block is less than our desired
* hashval, then we run with it.
*/
if (bno > 0) {
error = xfs_da_read_buf(trans, dp, bno, -2, &bp, XFS_DATA_FORK);
if ((error != 0) && (error != EFSCORRUPTED))
return(error);
if (bp)
leaf = bp->data;
if (bp && INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) != XFS_DIR_LEAF_MAGIC) {
xfs_dir_trace_g_dub("node: block not a leaf",
dp, uio, bno);
xfs_da_brelse(trans, bp);
bp = NULL;
}
if (bp && INT_GET(leaf->entries[0].hashval, ARCH_CONVERT) > cookhash) {
xfs_dir_trace_g_dub("node: leaf hash too large",
dp, uio, bno);
xfs_da_brelse(trans, bp);
bp = NULL;
}
if (bp &&
cookhash > INT_GET(leaf->entries[INT_GET(leaf->hdr.count, ARCH_CONVERT) - 1].hashval, ARCH_CONVERT)) {
xfs_dir_trace_g_dub("node: leaf hash too small",
dp, uio, bno);
xfs_da_brelse(trans, bp);
bp = NULL;
}
}
/*
* If we did not find a leaf block from the blockno in the cookie,
* or we there was no blockno in the cookie (eg: first time thru),
* the we start at the top of the Btree and re-find our hashval.
*/
if (bp == NULL) {
xfs_dir_trace_g_du("node: start at root" , dp, uio);
bno = 0;
for (;;) {
error = xfs_da_read_buf(trans, dp, bno, -1, &bp,
XFS_DATA_FORK);
if (error)
return(error);
if (bp == NULL)
return(XFS_ERROR(EFSCORRUPTED));
node = bp->data;
if (INT_GET(node->hdr.info.magic, ARCH_CONVERT) != XFS_DA_NODE_MAGIC)
break;
btree = &node->btree[0];
xfs_dir_trace_g_dun("node: node detail", dp, uio, node);
for (i = 0; i < INT_GET(node->hdr.count, ARCH_CONVERT); btree++, i++) {
if (INT_GET(btree->hashval, ARCH_CONVERT) >= cookhash) {
bno = INT_GET(btree->before, ARCH_CONVERT);
break;
}
}
if (i == INT_GET(node->hdr.count, ARCH_CONVERT)) {
xfs_da_brelse(trans, bp);
xfs_dir_trace_g_du("node: hash beyond EOF",
dp, uio);
uio->uio_offset = XFS_DA_MAKE_COOKIE(mp, 0, 0,
XFS_DA_MAXHASH);
*eofp = 1;
return(0);
}
xfs_dir_trace_g_dub("node: going to block",
dp, uio, bno);
xfs_da_brelse(trans, bp);
}
}
ASSERT(cookhash != XFS_DA_MAXHASH);
/*
* We've dropped down to the (first) leaf block that contains the
* hashval we are interested in. Continue rolling upward thru the
* leaf blocks until we fill up our buffer.
*/
for (;;) {
leaf = bp->data;
if (unlikely(INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) != XFS_DIR_LEAF_MAGIC)) {
xfs_dir_trace_g_dul("node: not a leaf", dp, uio, leaf);
xfs_da_brelse(trans, bp);
XFS_CORRUPTION_ERROR("xfs_dir_node_getdents(1)",
XFS_ERRLEVEL_LOW, mp, leaf);
return XFS_ERROR(EFSCORRUPTED);
}
xfs_dir_trace_g_dul("node: leaf detail", dp, uio, leaf);
if ((nextbno = INT_GET(leaf->hdr.info.forw, ARCH_CONVERT))) {
nextda = xfs_da_reada_buf(trans, dp, nextbno,
XFS_DATA_FORK);
} else
nextda = -1;
error = xfs_dir_leaf_getdents_int(bp, dp, bno, uio, &eob, dbp,
put, nextda);
xfs_da_brelse(trans, bp);
bno = nextbno;
if (eob) {
xfs_dir_trace_g_dub("node: E-O-B", dp, uio, bno);
*eofp = 0;
return(error);
}
if (bno == 0)
break;
error = xfs_da_read_buf(trans, dp, bno, nextda, &bp,
XFS_DATA_FORK);
if (error)
return(error);
if (unlikely(bp == NULL)) {
XFS_ERROR_REPORT("xfs_dir_node_getdents(2)",
XFS_ERRLEVEL_LOW, mp);
return(XFS_ERROR(EFSCORRUPTED));
}
}
*eofp = 1;
xfs_dir_trace_g_du("node: E-O-F", dp, uio);
return(0);
}
/*
* Look up a filename in an int directory, replace the inode number.
* Use an internal routine to actually do the lookup.
*/
STATIC int
xfs_dir_node_replace(xfs_da_args_t *args)
{
xfs_da_state_t *state;
xfs_da_state_blk_t *blk;
xfs_dir_leafblock_t *leaf;
xfs_dir_leaf_entry_t *entry;
xfs_dir_leaf_name_t *namest;
xfs_ino_t inum;
int retval, error, i;
xfs_dabuf_t *bp;
state = xfs_da_state_alloc();
state->args = args;
state->mp = args->dp->i_mount;
state->blocksize = state->mp->m_sb.sb_blocksize;
state->node_ents = state->mp->m_dir_node_ents;
inum = args->inumber;
/*
* Search to see if name exists,
* and get back a pointer to it.
*/
error = xfs_da_node_lookup_int(state, &retval);
if (error) {
retval = error;
}
if (retval == EEXIST) {
blk = &state->path.blk[state->path.active - 1];
ASSERT(blk->magic == XFS_DIR_LEAF_MAGIC);
bp = blk->bp;
leaf = bp->data;
entry = &leaf->entries[blk->index];
namest = XFS_DIR_LEAF_NAMESTRUCT(leaf, INT_GET(entry->nameidx, ARCH_CONVERT));
/* XXX - replace assert ? */
XFS_DIR_SF_PUT_DIRINO(&inum, &namest->inumber);
xfs_da_log_buf(args->trans, bp,
XFS_DA_LOGRANGE(leaf, namest, sizeof(namest->inumber)));
xfs_da_buf_done(bp);
blk->bp = NULL;
retval = 0;
} else {
i = state->path.active - 1;
xfs_da_brelse(args->trans, state->path.blk[i].bp);
state->path.blk[i].bp = NULL;
}
for (i = 0; i < state->path.active - 1; i++) {
xfs_da_brelse(args->trans, state->path.blk[i].bp);
state->path.blk[i].bp = NULL;
}
xfs_da_state_free(state);
return(retval);
}
#if defined(XFS_DIR_TRACE)
/*
* Add a trace buffer entry for an inode and a uio.
*/
void
xfs_dir_trace_g_du(char *where, xfs_inode_t *dp, uio_t *uio)
{
xfs_dir_trace_enter(XFS_DIR_KTRACE_G_DU, where,
(void *)dp, (void *)dp->i_mount,
(void *)((unsigned long)(uio->uio_offset >> 32)),
(void *)((unsigned long)(uio->uio_offset & 0xFFFFFFFF)),
(void *)(unsigned long)uio->uio_resid,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
}
/*
* Add a trace buffer entry for an inode and a uio.
*/
void
xfs_dir_trace_g_dub(char *where, xfs_inode_t *dp, uio_t *uio, xfs_dablk_t bno)
{
xfs_dir_trace_enter(XFS_DIR_KTRACE_G_DUB, where,
(void *)dp, (void *)dp->i_mount,
(void *)((unsigned long)(uio->uio_offset >> 32)),
(void *)((unsigned long)(uio->uio_offset & 0xFFFFFFFF)),
(void *)(unsigned long)uio->uio_resid,
(void *)(unsigned long)bno,
NULL, NULL, NULL, NULL, NULL, NULL);
}
/*
* Add a trace buffer entry for an inode and a uio.
*/
void
xfs_dir_trace_g_dun(char *where, xfs_inode_t *dp, uio_t *uio,
xfs_da_intnode_t *node)
{
int last = INT_GET(node->hdr.count, ARCH_CONVERT) - 1;
xfs_dir_trace_enter(XFS_DIR_KTRACE_G_DUN, where,
(void *)dp, (void *)dp->i_mount,
(void *)((unsigned long)(uio->uio_offset >> 32)),
(void *)((unsigned long)(uio->uio_offset & 0xFFFFFFFF)),
(void *)(unsigned long)uio->uio_resid,
(void *)(unsigned long)
INT_GET(node->hdr.info.forw, ARCH_CONVERT),
(void *)(unsigned long)
INT_GET(node->hdr.count, ARCH_CONVERT),
(void *)(unsigned long)
INT_GET(node->btree[0].hashval, ARCH_CONVERT),
(void *)(unsigned long)
INT_GET(node->btree[last].hashval, ARCH_CONVERT),
NULL, NULL, NULL);
}
/*
* Add a trace buffer entry for an inode and a uio.
*/
void
xfs_dir_trace_g_dul(char *where, xfs_inode_t *dp, uio_t *uio,
xfs_dir_leafblock_t *leaf)
{
int last = INT_GET(leaf->hdr.count, ARCH_CONVERT) - 1;
xfs_dir_trace_enter(XFS_DIR_KTRACE_G_DUL, where,
(void *)dp, (void *)dp->i_mount,
(void *)((unsigned long)(uio->uio_offset >> 32)),
(void *)((unsigned long)(uio->uio_offset & 0xFFFFFFFF)),
(void *)(unsigned long)uio->uio_resid,
(void *)(unsigned long)
INT_GET(leaf->hdr.info.forw, ARCH_CONVERT),
(void *)(unsigned long)
INT_GET(leaf->hdr.count, ARCH_CONVERT),
(void *)(unsigned long)
INT_GET(leaf->entries[0].hashval, ARCH_CONVERT),
(void *)(unsigned long)
INT_GET(leaf->entries[last].hashval, ARCH_CONVERT),
NULL, NULL, NULL);
}
/*
* Add a trace buffer entry for an inode and a uio.
*/
void
xfs_dir_trace_g_due(char *where, xfs_inode_t *dp, uio_t *uio,
xfs_dir_leaf_entry_t *entry)
{
xfs_dir_trace_enter(XFS_DIR_KTRACE_G_DUE, where,
(void *)dp, (void *)dp->i_mount,
(void *)((unsigned long)(uio->uio_offset >> 32)),
(void *)((unsigned long)(uio->uio_offset & 0xFFFFFFFF)),
(void *)(unsigned long)uio->uio_resid,
(void *)(unsigned long)
INT_GET(entry->hashval, ARCH_CONVERT),
NULL, NULL, NULL, NULL, NULL, NULL);
}
/*
* Add a trace buffer entry for an inode and a uio.
*/
void
xfs_dir_trace_g_duc(char *where, xfs_inode_t *dp, uio_t *uio, xfs_off_t cookie)
{
xfs_dir_trace_enter(XFS_DIR_KTRACE_G_DUC, where,
(void *)dp, (void *)dp->i_mount,
(void *)((unsigned long)(uio->uio_offset >> 32)),
(void *)((unsigned long)(uio->uio_offset & 0xFFFFFFFF)),
(void *)(unsigned long)uio->uio_resid,
(void *)((unsigned long)(cookie >> 32)),
(void *)((unsigned long)(cookie & 0xFFFFFFFF)),
NULL, NULL, NULL, NULL, NULL);
}
/*
* Add a trace buffer entry for the arguments given to the routine,
* generic form.
*/
void
xfs_dir_trace_enter(int type, char *where,
void * a0, void * a1,
void * a2, void * a3,
void * a4, void * a5,
void * a6, void * a7,
void * a8, void * a9,
void * a10, void * a11)
{
ASSERT(xfs_dir_trace_buf);
ktrace_enter(xfs_dir_trace_buf, (void *)(unsigned long)type,
(void *)where,
(void *)a0, (void *)a1, (void *)a2,
(void *)a3, (void *)a4, (void *)a5,
(void *)a6, (void *)a7, (void *)a8,
(void *)a9, (void *)a10, (void *)a11,
NULL, NULL);
}
#endif /* XFS_DIR_TRACE */