// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2000-2005 Silicon Graphics, Inc. * All Rights Reserved. */ #include "xfs.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_defer.h" #include "xfs_da_format.h" #include "xfs_da_btree.h" #include "xfs_attr_sf.h" #include "xfs_inode.h" #include "xfs_trans.h" #include "xfs_bmap.h" #include "xfs_bmap_btree.h" #include "xfs_attr.h" #include "xfs_attr_leaf.h" #include "xfs_attr_remote.h" #include "xfs_quota.h" #include "xfs_trans_space.h" #include "xfs_trace.h" /* * xfs_attr.c * * Provide the external interfaces to manage attribute lists. */ /*======================================================================== * Function prototypes for the kernel. *========================================================================*/ /* * Internal routines when attribute list fits inside the inode. */ STATIC int xfs_attr_shortform_addname(xfs_da_args_t *args); /* * Internal routines when attribute list is one block. */ STATIC int xfs_attr_leaf_get(xfs_da_args_t *args); STATIC int xfs_attr_leaf_addname(xfs_da_args_t *args); STATIC int xfs_attr_leaf_removename(xfs_da_args_t *args); /* * Internal routines when attribute list is more than one block. */ STATIC int xfs_attr_node_get(xfs_da_args_t *args); STATIC int xfs_attr_node_addname(xfs_da_args_t *args); STATIC int xfs_attr_node_removename(xfs_da_args_t *args); STATIC int xfs_attr_fillstate(xfs_da_state_t *state); STATIC int xfs_attr_refillstate(xfs_da_state_t *state); int xfs_inode_hasattr( struct xfs_inode *ip) { if (!XFS_IFORK_Q(ip) || (ip->i_d.di_aformat == XFS_DINODE_FMT_EXTENTS && ip->i_d.di_anextents == 0)) return 0; return 1; } /*======================================================================== * Overall external interface routines. *========================================================================*/ /* * Retrieve an extended attribute and its value. Must have ilock. * Returns 0 on successful retrieval, otherwise an error. */ int xfs_attr_get_ilocked( struct xfs_da_args *args) { ASSERT(xfs_isilocked(args->dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL)); if (!xfs_inode_hasattr(args->dp)) return -ENOATTR; if (args->dp->i_d.di_aformat == XFS_DINODE_FMT_LOCAL) return xfs_attr_shortform_getvalue(args); if (xfs_bmap_one_block(args->dp, XFS_ATTR_FORK)) return xfs_attr_leaf_get(args); return xfs_attr_node_get(args); } /* * Retrieve an extended attribute by name, and its value if requested. * * If args->valuelen is zero, then the caller does not want the value, just an * indication whether the attribute exists and the size of the value if it * exists. The size is returned in args.valuelen. * * If args->value is NULL but args->valuelen is non-zero, allocate the buffer * for the value after existence of the attribute has been determined. The * caller always has to free args->value if it is set, no matter if this * function was successful or not. * * If the attribute is found, but exceeds the size limit set by the caller in * args->valuelen, return -ERANGE with the size of the attribute that was found * in args->valuelen. */ int xfs_attr_get( struct xfs_da_args *args) { uint lock_mode; int error; XFS_STATS_INC(args->dp->i_mount, xs_attr_get); if (XFS_FORCED_SHUTDOWN(args->dp->i_mount)) return -EIO; args->geo = args->dp->i_mount->m_attr_geo; args->whichfork = XFS_ATTR_FORK; args->hashval = xfs_da_hashname(args->name, args->namelen); /* Entirely possible to look up a name which doesn't exist */ args->op_flags = XFS_DA_OP_OKNOENT; lock_mode = xfs_ilock_attr_map_shared(args->dp); error = xfs_attr_get_ilocked(args); xfs_iunlock(args->dp, lock_mode); return error; } /* * Calculate how many blocks we need for the new attribute, */ STATIC int xfs_attr_calc_size( struct xfs_da_args *args, int *local) { struct xfs_mount *mp = args->dp->i_mount; int size; int nblks; /* * Determine space new attribute will use, and if it would be * "local" or "remote" (note: local != inline). */ size = xfs_attr_leaf_newentsize(args, local); nblks = XFS_DAENTER_SPACE_RES(mp, XFS_ATTR_FORK); if (*local) { if (size > (args->geo->blksize / 2)) { /* Double split possible */ nblks *= 2; } } else { /* * Out of line attribute, cannot double split, but * make room for the attribute value itself. */ uint dblocks = xfs_attr3_rmt_blocks(mp, args->valuelen); nblks += dblocks; nblks += XFS_NEXTENTADD_SPACE_RES(mp, dblocks, XFS_ATTR_FORK); } return nblks; } STATIC int xfs_attr_try_sf_addname( struct xfs_inode *dp, struct xfs_da_args *args) { struct xfs_mount *mp = dp->i_mount; int error, error2; error = xfs_attr_shortform_addname(args); if (error == -ENOSPC) return error; /* * Commit the shortform mods, and we're done. * NOTE: this is also the error path (EEXIST, etc). */ if (!error && (args->flags & ATTR_KERNOTIME) == 0) xfs_trans_ichgtime(args->trans, dp, XFS_ICHGTIME_CHG); if (mp->m_flags & XFS_MOUNT_WSYNC) xfs_trans_set_sync(args->trans); error2 = xfs_trans_commit(args->trans); args->trans = NULL; return error ? error : error2; } /* * Set the attribute specified in @args. */ int xfs_attr_set_args( struct xfs_da_args *args) { struct xfs_inode *dp = args->dp; struct xfs_buf *leaf_bp = NULL; int error; /* * If the attribute list is non-existent or a shortform list, * upgrade it to a single-leaf-block attribute list. */ if (dp->i_d.di_aformat == XFS_DINODE_FMT_LOCAL || (dp->i_d.di_aformat == XFS_DINODE_FMT_EXTENTS && dp->i_d.di_anextents == 0)) { /* * Build initial attribute list (if required). */ if (dp->i_d.di_aformat == XFS_DINODE_FMT_EXTENTS) xfs_attr_shortform_create(args); /* * Try to add the attr to the attribute list in the inode. */ error = xfs_attr_try_sf_addname(dp, args); if (error != -ENOSPC) return error; /* * It won't fit in the shortform, transform to a leaf block. * GROT: another possible req'mt for a double-split btree op. */ error = xfs_attr_shortform_to_leaf(args, &leaf_bp); if (error) return error; /* * Prevent the leaf buffer from being unlocked so that a * concurrent AIL push cannot grab the half-baked leaf * buffer and run into problems with the write verifier. * Once we're done rolling the transaction we can release * the hold and add the attr to the leaf. */ xfs_trans_bhold(args->trans, leaf_bp); error = xfs_defer_finish(&args->trans); xfs_trans_bhold_release(args->trans, leaf_bp); if (error) { xfs_trans_brelse(args->trans, leaf_bp); return error; } } if (xfs_bmap_one_block(dp, XFS_ATTR_FORK)) error = xfs_attr_leaf_addname(args); else error = xfs_attr_node_addname(args); return error; } /* * Remove the attribute specified in @args. */ int xfs_attr_remove_args( struct xfs_da_args *args) { struct xfs_inode *dp = args->dp; int error; if (!xfs_inode_hasattr(dp)) { error = -ENOATTR; } else if (dp->i_d.di_aformat == XFS_DINODE_FMT_LOCAL) { ASSERT(dp->i_afp->if_flags & XFS_IFINLINE); error = xfs_attr_shortform_remove(args); } else if (xfs_bmap_one_block(dp, XFS_ATTR_FORK)) { error = xfs_attr_leaf_removename(args); } else { error = xfs_attr_node_removename(args); } return error; } /* * Note: If args->value is NULL the attribute will be removed, just like the * Linux ->setattr API. */ int xfs_attr_set( struct xfs_da_args *args) { struct xfs_inode *dp = args->dp; struct xfs_mount *mp = dp->i_mount; struct xfs_trans_res tres; int rsvd = (args->flags & ATTR_ROOT) != 0; int error, local; unsigned int total; if (XFS_FORCED_SHUTDOWN(dp->i_mount)) return -EIO; error = xfs_qm_dqattach(dp); if (error) return error; args->geo = mp->m_attr_geo; args->whichfork = XFS_ATTR_FORK; args->hashval = xfs_da_hashname(args->name, args->namelen); /* * We have no control over the attribute names that userspace passes us * to remove, so we have to allow the name lookup prior to attribute * removal to fail as well. */ args->op_flags = XFS_DA_OP_OKNOENT; if (args->value) { XFS_STATS_INC(mp, xs_attr_set); args->op_flags |= XFS_DA_OP_ADDNAME; args->total = xfs_attr_calc_size(args, &local); /* * If the inode doesn't have an attribute fork, add one. * (inode must not be locked when we call this routine) */ if (XFS_IFORK_Q(dp) == 0) { int sf_size = sizeof(struct xfs_attr_sf_hdr) + XFS_ATTR_SF_ENTSIZE_BYNAME(args->namelen, args->valuelen); error = xfs_bmap_add_attrfork(dp, sf_size, rsvd); if (error) return error; } tres.tr_logres = M_RES(mp)->tr_attrsetm.tr_logres + M_RES(mp)->tr_attrsetrt.tr_logres * args->total; tres.tr_logcount = XFS_ATTRSET_LOG_COUNT; tres.tr_logflags = XFS_TRANS_PERM_LOG_RES; total = args->total; } else { XFS_STATS_INC(mp, xs_attr_remove); tres = M_RES(mp)->tr_attrrm; total = XFS_ATTRRM_SPACE_RES(mp); } /* * Root fork attributes can use reserved data blocks for this * operation if necessary */ error = xfs_trans_alloc(mp, &tres, total, 0, rsvd ? XFS_TRANS_RESERVE : 0, &args->trans); if (error) return error; xfs_ilock(dp, XFS_ILOCK_EXCL); xfs_trans_ijoin(args->trans, dp, 0); if (args->value) { unsigned int quota_flags = XFS_QMOPT_RES_REGBLKS; if (rsvd) quota_flags |= XFS_QMOPT_FORCE_RES; error = xfs_trans_reserve_quota_nblks(args->trans, dp, args->total, 0, quota_flags); if (error) goto out_trans_cancel; error = xfs_attr_set_args(args); if (error) goto out_trans_cancel; /* shortform attribute has already been committed */ if (!args->trans) goto out_unlock; } else { error = xfs_attr_remove_args(args); if (error) goto out_trans_cancel; } /* * If this is a synchronous mount, make sure that the * transaction goes to disk before returning to the user. */ if (mp->m_flags & XFS_MOUNT_WSYNC) xfs_trans_set_sync(args->trans); if ((args->flags & ATTR_KERNOTIME) == 0) xfs_trans_ichgtime(args->trans, dp, XFS_ICHGTIME_CHG); /* * Commit the last in the sequence of transactions. */ xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE); error = xfs_trans_commit(args->trans); out_unlock: xfs_iunlock(dp, XFS_ILOCK_EXCL); return error; out_trans_cancel: if (args->trans) xfs_trans_cancel(args->trans); goto out_unlock; } /*======================================================================== * External routines when attribute list is inside the inode *========================================================================*/ /* * Add a name to the shortform attribute list structure * This is the external routine. */ STATIC int xfs_attr_shortform_addname(xfs_da_args_t *args) { int newsize, forkoff, retval; trace_xfs_attr_sf_addname(args); retval = xfs_attr_shortform_lookup(args); if ((args->flags & ATTR_REPLACE) && (retval == -ENOATTR)) { return retval; } else if (retval == -EEXIST) { if (args->flags & ATTR_CREATE) return retval; retval = xfs_attr_shortform_remove(args); if (retval) return retval; /* * Since we have removed the old attr, clear ATTR_REPLACE so * that the leaf format add routine won't trip over the attr * not being around. */ args->flags &= ~ATTR_REPLACE; } if (args->namelen >= XFS_ATTR_SF_ENTSIZE_MAX || args->valuelen >= XFS_ATTR_SF_ENTSIZE_MAX) return -ENOSPC; newsize = XFS_ATTR_SF_TOTSIZE(args->dp); newsize += XFS_ATTR_SF_ENTSIZE_BYNAME(args->namelen, args->valuelen); forkoff = xfs_attr_shortform_bytesfit(args->dp, newsize); if (!forkoff) return -ENOSPC; xfs_attr_shortform_add(args, forkoff); return 0; } /*======================================================================== * External routines when attribute list is one block *========================================================================*/ /* * Add a name to the leaf attribute list structure * * This leaf block cannot have a "remote" value, we only call this routine * if bmap_one_block() says there is only one block (ie: no remote blks). */ STATIC int xfs_attr_leaf_addname( struct xfs_da_args *args) { struct xfs_inode *dp; struct xfs_buf *bp; int retval, error, forkoff; trace_xfs_attr_leaf_addname(args); /* * Read the (only) block in the attribute list in. */ dp = args->dp; args->blkno = 0; error = xfs_attr3_leaf_read(args->trans, args->dp, args->blkno, &bp); if (error) return error; /* * Look up the given attribute in the leaf block. Figure out if * the given flags produce an error or call for an atomic rename. */ retval = xfs_attr3_leaf_lookup_int(bp, args); if ((args->flags & ATTR_REPLACE) && (retval == -ENOATTR)) { xfs_trans_brelse(args->trans, bp); return retval; } else if (retval == -EEXIST) { if (args->flags & ATTR_CREATE) { /* pure create op */ xfs_trans_brelse(args->trans, bp); return retval; } trace_xfs_attr_leaf_replace(args); /* save the attribute state for later removal*/ args->op_flags |= XFS_DA_OP_RENAME; /* an atomic rename */ args->blkno2 = args->blkno; /* set 2nd entry info*/ args->index2 = args->index; args->rmtblkno2 = args->rmtblkno; args->rmtblkcnt2 = args->rmtblkcnt; args->rmtvaluelen2 = args->rmtvaluelen; /* * clear the remote attr state now that it is saved so that the * values reflect the state of the attribute we are about to * add, not the attribute we just found and will remove later. */ args->rmtblkno = 0; args->rmtblkcnt = 0; args->rmtvaluelen = 0; } /* * Add the attribute to the leaf block, transitioning to a Btree * if required. */ retval = xfs_attr3_leaf_add(bp, args); if (retval == -ENOSPC) { /* * Promote the attribute list to the Btree format, then * Commit that transaction so that the node_addname() call * can manage its own transactions. */ error = xfs_attr3_leaf_to_node(args); if (error) return error; error = xfs_defer_finish(&args->trans); if (error) return error; /* * Commit the current trans (including the inode) and start * a new one. */ error = xfs_trans_roll_inode(&args->trans, dp); if (error) return error; /* * Fob the whole rest of the problem off on the Btree code. */ error = xfs_attr_node_addname(args); return error; } /* * Commit the transaction that added the attr name so that * later routines can manage their own transactions. */ error = xfs_trans_roll_inode(&args->trans, dp); if (error) return error; /* * If there was an out-of-line value, allocate the blocks we * identified for its storage and copy the value. This is done * after we create the attribute so that we don't overflow the * maximum size of a transaction and/or hit a deadlock. */ if (args->rmtblkno > 0) { error = xfs_attr_rmtval_set(args); if (error) return error; } /* * If this is an atomic rename operation, we must "flip" the * incomplete flags on the "new" and "old" attribute/value pairs * so that one disappears and one appears atomically. Then we * must remove the "old" attribute/value pair. */ if (args->op_flags & XFS_DA_OP_RENAME) { /* * In a separate transaction, set the incomplete flag on the * "old" attr and clear the incomplete flag on the "new" attr. */ error = xfs_attr3_leaf_flipflags(args); if (error) return error; /* * Dismantle the "old" attribute/value pair by removing * a "remote" value (if it exists). */ args->index = args->index2; args->blkno = args->blkno2; args->rmtblkno = args->rmtblkno2; args->rmtblkcnt = args->rmtblkcnt2; args->rmtvaluelen = args->rmtvaluelen2; if (args->rmtblkno) { error = xfs_attr_rmtval_remove(args); if (error) return error; } /* * Read in the block containing the "old" attr, then * remove the "old" attr from that block (neat, huh!) */ error = xfs_attr3_leaf_read(args->trans, args->dp, args->blkno, &bp); if (error) return error; xfs_attr3_leaf_remove(bp, args); /* * If the result is small enough, shrink it all into the inode. */ if ((forkoff = xfs_attr_shortform_allfit(bp, dp))) { error = xfs_attr3_leaf_to_shortform(bp, args, forkoff); /* bp is gone due to xfs_da_shrink_inode */ if (error) return error; error = xfs_defer_finish(&args->trans); if (error) return error; } /* * Commit the remove and start the next trans in series. */ error = xfs_trans_roll_inode(&args->trans, dp); } else if (args->rmtblkno > 0) { /* * Added a "remote" value, just clear the incomplete flag. */ error = xfs_attr3_leaf_clearflag(args); } return error; } /* * Remove a name from the leaf attribute list structure * * This leaf block cannot have a "remote" value, we only call this routine * if bmap_one_block() says there is only one block (ie: no remote blks). */ STATIC int xfs_attr_leaf_removename( struct xfs_da_args *args) { struct xfs_inode *dp; struct xfs_buf *bp; int error, forkoff; trace_xfs_attr_leaf_removename(args); /* * Remove the attribute. */ dp = args->dp; args->blkno = 0; error = xfs_attr3_leaf_read(args->trans, args->dp, args->blkno, &bp); if (error) return error; error = xfs_attr3_leaf_lookup_int(bp, args); if (error == -ENOATTR) { xfs_trans_brelse(args->trans, bp); return error; } xfs_attr3_leaf_remove(bp, args); /* * If the result is small enough, shrink it all into the inode. */ if ((forkoff = xfs_attr_shortform_allfit(bp, dp))) { error = xfs_attr3_leaf_to_shortform(bp, args, forkoff); /* bp is gone due to xfs_da_shrink_inode */ if (error) return error; error = xfs_defer_finish(&args->trans); if (error) return error; } return 0; } /* * Look up a name in a leaf attribute list structure. * * This leaf block cannot have a "remote" value, we only call this routine * if bmap_one_block() says there is only one block (ie: no remote blks). * * Returns 0 on successful retrieval, otherwise an error. */ STATIC int xfs_attr_leaf_get(xfs_da_args_t *args) { struct xfs_buf *bp; int error; trace_xfs_attr_leaf_get(args); args->blkno = 0; error = xfs_attr3_leaf_read(args->trans, args->dp, args->blkno, &bp); if (error) return error; error = xfs_attr3_leaf_lookup_int(bp, args); if (error != -EEXIST) { xfs_trans_brelse(args->trans, bp); return error; } error = xfs_attr3_leaf_getvalue(bp, args); xfs_trans_brelse(args->trans, bp); return error; } /*======================================================================== * External routines when attribute list size > geo->blksize *========================================================================*/ /* * Add a name to a Btree-format attribute list. * * 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). * * "Remote" attribute values confuse the issue and atomic rename operations * add a whole extra layer of confusion on top of that. */ STATIC int xfs_attr_node_addname( struct xfs_da_args *args) { struct xfs_da_state *state; struct xfs_da_state_blk *blk; struct xfs_inode *dp; struct xfs_mount *mp; int retval, error; trace_xfs_attr_node_addname(args); /* * Fill in bucket of arguments/results/context to carry around. */ dp = args->dp; mp = dp->i_mount; restart: state = xfs_da_state_alloc(); state->args = args; state->mp = mp; /* * Search to see if name already exists, and get back a pointer * to where it should go. */ error = xfs_da3_node_lookup_int(state, &retval); if (error) goto out; blk = &state->path.blk[ state->path.active-1 ]; ASSERT(blk->magic == XFS_ATTR_LEAF_MAGIC); if ((args->flags & ATTR_REPLACE) && (retval == -ENOATTR)) { goto out; } else if (retval == -EEXIST) { if (args->flags & ATTR_CREATE) goto out; trace_xfs_attr_node_replace(args); /* save the attribute state for later removal*/ args->op_flags |= XFS_DA_OP_RENAME; /* atomic rename op */ args->blkno2 = args->blkno; /* set 2nd entry info*/ args->index2 = args->index; args->rmtblkno2 = args->rmtblkno; args->rmtblkcnt2 = args->rmtblkcnt; args->rmtvaluelen2 = args->rmtvaluelen; /* * clear the remote attr state now that it is saved so that the * values reflect the state of the attribute we are about to * add, not the attribute we just found and will remove later. */ args->rmtblkno = 0; args->rmtblkcnt = 0; args->rmtvaluelen = 0; } retval = xfs_attr3_leaf_add(blk->bp, state->args); if (retval == -ENOSPC) { if (state->path.active == 1) { /* * Its really a single leaf node, but it had * out-of-line values so it looked like it *might* * have been a b-tree. */ xfs_da_state_free(state); state = NULL; error = xfs_attr3_leaf_to_node(args); if (error) goto out; error = xfs_defer_finish(&args->trans); if (error) goto out; /* * Commit the node conversion and start the next * trans in the chain. */ error = xfs_trans_roll_inode(&args->trans, dp); if (error) goto out; goto restart; } /* * Split as many Btree elements as required. * This code tracks the new and old attr's location * in the index/blkno/rmtblkno/rmtblkcnt fields and * in the index2/blkno2/rmtblkno2/rmtblkcnt2 fields. */ error = xfs_da3_split(state); if (error) goto out; error = xfs_defer_finish(&args->trans); if (error) goto out; } else { /* * Addition succeeded, update Btree hashvals. */ xfs_da3_fixhashpath(state, &state->path); } /* * Kill the state structure, we're done with it and need to * allow the buffers to come back later. */ xfs_da_state_free(state); state = NULL; /* * Commit the leaf addition or btree split and start the next * trans in the chain. */ error = xfs_trans_roll_inode(&args->trans, dp); if (error) goto out; /* * If there was an out-of-line value, allocate the blocks we * identified for its storage and copy the value. This is done * after we create the attribute so that we don't overflow the * maximum size of a transaction and/or hit a deadlock. */ if (args->rmtblkno > 0) { error = xfs_attr_rmtval_set(args); if (error) return error; } /* * If this is an atomic rename operation, we must "flip" the * incomplete flags on the "new" and "old" attribute/value pairs * so that one disappears and one appears atomically. Then we * must remove the "old" attribute/value pair. */ if (args->op_flags & XFS_DA_OP_RENAME) { /* * In a separate transaction, set the incomplete flag on the * "old" attr and clear the incomplete flag on the "new" attr. */ error = xfs_attr3_leaf_flipflags(args); if (error) goto out; /* * Dismantle the "old" attribute/value pair by removing * a "remote" value (if it exists). */ args->index = args->index2; args->blkno = args->blkno2; args->rmtblkno = args->rmtblkno2; args->rmtblkcnt = args->rmtblkcnt2; args->rmtvaluelen = args->rmtvaluelen2; if (args->rmtblkno) { error = xfs_attr_rmtval_remove(args); if (error) return error; } /* * Re-find the "old" attribute entry after any split ops. * The INCOMPLETE flag means that we will find the "old" * attr, not the "new" one. */ args->op_flags |= XFS_DA_OP_INCOMPLETE; state = xfs_da_state_alloc(); state->args = args; state->mp = mp; state->inleaf = 0; error = xfs_da3_node_lookup_int(state, &retval); if (error) goto out; /* * Remove the name and update the hashvals in the tree. */ blk = &state->path.blk[ state->path.active-1 ]; ASSERT(blk->magic == XFS_ATTR_LEAF_MAGIC); error = xfs_attr3_leaf_remove(blk->bp, args); xfs_da3_fixhashpath(state, &state->path); /* * Check to see if the tree needs to be collapsed. */ if (retval && (state->path.active > 1)) { error = xfs_da3_join(state); if (error) goto out; error = xfs_defer_finish(&args->trans); if (error) goto out; } /* * Commit and start the next trans in the chain. */ error = xfs_trans_roll_inode(&args->trans, dp); if (error) goto out; } else if (args->rmtblkno > 0) { /* * Added a "remote" value, just clear the incomplete flag. */ error = xfs_attr3_leaf_clearflag(args); if (error) goto out; } retval = error = 0; out: if (state) xfs_da_state_free(state); if (error) return error; return retval; } /* * Remove a name from a B-tree attribute list. * * 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_attr_node_removename( struct xfs_da_args *args) { struct xfs_da_state *state; struct xfs_da_state_blk *blk; struct xfs_inode *dp; struct xfs_buf *bp; int retval, error, forkoff; trace_xfs_attr_node_removename(args); /* * Tie a string around our finger to remind us where we are. */ dp = args->dp; state = xfs_da_state_alloc(); state->args = args; state->mp = dp->i_mount; /* * Search to see if name exists, and get back a pointer to it. */ error = xfs_da3_node_lookup_int(state, &retval); if (error || (retval != -EEXIST)) { if (error == 0) error = retval; goto out; } /* * If there is an out-of-line value, de-allocate the blocks. * This is done before we remove the attribute so that we don't * overflow the maximum size of a transaction and/or hit a deadlock. */ blk = &state->path.blk[ state->path.active-1 ]; ASSERT(blk->bp != NULL); ASSERT(blk->magic == XFS_ATTR_LEAF_MAGIC); if (args->rmtblkno > 0) { /* * Fill in disk block numbers in the state structure * so that we can get the buffers back after we commit * several transactions in the following calls. */ error = xfs_attr_fillstate(state); if (error) goto out; /* * Mark the attribute as INCOMPLETE, then bunmapi() the * remote value. */ error = xfs_attr3_leaf_setflag(args); if (error) goto out; error = xfs_attr_rmtval_remove(args); if (error) goto out; /* * Refill the state structure with buffers, the prior calls * released our buffers. */ error = xfs_attr_refillstate(state); if (error) goto out; } /* * Remove the name and update the hashvals in the tree. */ blk = &state->path.blk[ state->path.active-1 ]; ASSERT(blk->magic == XFS_ATTR_LEAF_MAGIC); retval = xfs_attr3_leaf_remove(blk->bp, args); xfs_da3_fixhashpath(state, &state->path); /* * Check to see if the tree needs to be collapsed. */ if (retval && (state->path.active > 1)) { error = xfs_da3_join(state); if (error) goto out; error = xfs_defer_finish(&args->trans); if (error) goto out; /* * Commit the Btree join operation and start a new trans. */ error = xfs_trans_roll_inode(&args->trans, dp); if (error) goto out; } /* * If the result is small enough, push it all into the inode. */ if (xfs_bmap_one_block(dp, XFS_ATTR_FORK)) { /* * Have to get rid of the copy of this dabuf in the state. */ ASSERT(state->path.active == 1); ASSERT(state->path.blk[0].bp); state->path.blk[0].bp = NULL; error = xfs_attr3_leaf_read(args->trans, args->dp, 0, &bp); if (error) goto out; if ((forkoff = xfs_attr_shortform_allfit(bp, dp))) { error = xfs_attr3_leaf_to_shortform(bp, args, forkoff); /* bp is gone due to xfs_da_shrink_inode */ if (error) goto out; error = xfs_defer_finish(&args->trans); if (error) goto out; } else xfs_trans_brelse(args->trans, bp); } error = 0; out: xfs_da_state_free(state); return error; } /* * Fill in the disk block numbers in the state structure for the buffers * that are attached to the state structure. * This is done so that we can quickly reattach ourselves to those buffers * after some set of transaction commits have released these buffers. */ STATIC int xfs_attr_fillstate(xfs_da_state_t *state) { xfs_da_state_path_t *path; xfs_da_state_blk_t *blk; int level; trace_xfs_attr_fillstate(state->args); /* * Roll down the "path" in the state structure, storing the on-disk * block number for those buffers in the "path". */ path = &state->path; ASSERT((path->active >= 0) && (path->active < XFS_DA_NODE_MAXDEPTH)); for (blk = path->blk, level = 0; level < path->active; blk++, level++) { if (blk->bp) { blk->disk_blkno = XFS_BUF_ADDR(blk->bp); blk->bp = NULL; } else { blk->disk_blkno = 0; } } /* * Roll down the "altpath" in the state structure, storing the on-disk * block number for those buffers in the "altpath". */ path = &state->altpath; ASSERT((path->active >= 0) && (path->active < XFS_DA_NODE_MAXDEPTH)); for (blk = path->blk, level = 0; level < path->active; blk++, level++) { if (blk->bp) { blk->disk_blkno = XFS_BUF_ADDR(blk->bp); blk->bp = NULL; } else { blk->disk_blkno = 0; } } return 0; } /* * Reattach the buffers to the state structure based on the disk block * numbers stored in the state structure. * This is done after some set of transaction commits have released those * buffers from our grip. */ STATIC int xfs_attr_refillstate(xfs_da_state_t *state) { xfs_da_state_path_t *path; xfs_da_state_blk_t *blk; int level, error; trace_xfs_attr_refillstate(state->args); /* * Roll down the "path" in the state structure, storing the on-disk * block number for those buffers in the "path". */ path = &state->path; ASSERT((path->active >= 0) && (path->active < XFS_DA_NODE_MAXDEPTH)); for (blk = path->blk, level = 0; level < path->active; blk++, level++) { if (blk->disk_blkno) { error = xfs_da3_node_read_mapped(state->args->trans, state->args->dp, blk->disk_blkno, &blk->bp, XFS_ATTR_FORK); if (error) return error; } else { blk->bp = NULL; } } /* * Roll down the "altpath" in the state structure, storing the on-disk * block number for those buffers in the "altpath". */ path = &state->altpath; ASSERT((path->active >= 0) && (path->active < XFS_DA_NODE_MAXDEPTH)); for (blk = path->blk, level = 0; level < path->active; blk++, level++) { if (blk->disk_blkno) { error = xfs_da3_node_read_mapped(state->args->trans, state->args->dp, blk->disk_blkno, &blk->bp, XFS_ATTR_FORK); if (error) return error; } else { blk->bp = NULL; } } return 0; } /* * Retrieve the attribute data from a node attribute list. * * This routine gets called for any attribute fork that has more than one * block, ie: both true Btree attr lists and for single-leaf-blocks with * "remote" values taking up more blocks. * * Returns 0 on successful retrieval, otherwise an error. */ STATIC int xfs_attr_node_get(xfs_da_args_t *args) { xfs_da_state_t *state; xfs_da_state_blk_t *blk; int error, retval; int i; trace_xfs_attr_node_get(args); state = xfs_da_state_alloc(); state->args = args; state->mp = args->dp->i_mount; /* * Search to see if name exists, and get back a pointer to it. */ error = xfs_da3_node_lookup_int(state, &retval); if (error) { retval = error; goto out_release; } if (retval != -EEXIST) goto out_release; /* * Get the value, local or "remote" */ blk = &state->path.blk[state->path.active - 1]; retval = xfs_attr3_leaf_getvalue(blk->bp, args); /* * If not in a transaction, we have to release all the buffers. */ out_release: for (i = 0; i < state->path.active; i++) { xfs_trans_brelse(args->trans, state->path.blk[i].bp); state->path.blk[i].bp = NULL; } xfs_da_state_free(state); return retval; } /* Returns true if the attribute entry name is valid. */ bool xfs_attr_namecheck( const void *name, size_t length) { /* * MAXNAMELEN includes the trailing null, but (name/length) leave it * out, so use >= for the length check. */ if (length >= MAXNAMELEN) return false; /* There shouldn't be any nulls here */ return !memchr(name, 0, length); }