// 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" #include "xfs_attr_item.h" #include "xfs_xattr.h" struct kmem_cache *xfs_attr_intent_cache; /* * 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_removename(xfs_da_args_t *args); STATIC int xfs_attr_leaf_hasname(struct xfs_da_args *args, struct xfs_buf **bp); STATIC int xfs_attr_leaf_try_add(struct xfs_da_args *args); /* * Internal routines when attribute list is more than one block. */ STATIC int xfs_attr_node_get(xfs_da_args_t *args); STATIC void xfs_attr_restore_rmt_blk(struct xfs_da_args *args); static int xfs_attr_node_try_addname(struct xfs_attr_intent *attr); STATIC int xfs_attr_node_addname_find_attr(struct xfs_attr_intent *attr); STATIC int xfs_attr_node_remove_attr(struct xfs_attr_intent *attr); STATIC int xfs_attr_node_lookup(struct xfs_da_args *args, struct xfs_da_state *state); int xfs_inode_hasattr( struct xfs_inode *ip) { if (!xfs_inode_has_attr_fork(ip)) return 0; if (ip->i_af.if_format == XFS_DINODE_FMT_EXTENTS && ip->i_af.if_nextents == 0) return 0; return 1; } /* * Returns true if the there is exactly only block in the attr fork, in which * case the attribute fork consists of a single leaf block entry. */ bool xfs_attr_is_leaf( struct xfs_inode *ip) { struct xfs_ifork *ifp = &ip->i_af; struct xfs_iext_cursor icur; struct xfs_bmbt_irec imap; if (ifp->if_nextents != 1 || ifp->if_format != XFS_DINODE_FMT_EXTENTS) return false; xfs_iext_first(ifp, &icur); xfs_iext_get_extent(ifp, &icur, &imap); return imap.br_startoff == 0 && imap.br_blockcount == 1; } /* * XXX (dchinner): name path state saving and refilling is an optimisation to * avoid needing to look up name entries after rolling transactions removing * remote xattr blocks between the name entry lookup and name entry removal. * This optimisation got sidelined when combining the set and remove state * machines, but the code has been left in place because it is worthwhile to * restore the optimisation once the combined state machine paths have settled. * * This comment is a public service announcement to remind Future Dave that he * still needs to restore this code to working order. */ #if 0 /* * 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_daddr(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_daddr(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; } #else static int xfs_attr_fillstate(xfs_da_state_t *state) { return 0; } #endif /*======================================================================== * 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) { xfs_assert_ilocked(args->dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL); if (!xfs_inode_hasattr(args->dp)) return -ENOATTR; if (args->dp->i_af.if_format == XFS_DINODE_FMT_LOCAL) return xfs_attr_shortform_getvalue(args); if (xfs_attr_is_leaf(args->dp)) 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_is_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, */ 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; } /* Initialize transaction reservation for attr operations */ void xfs_init_attr_trans( struct xfs_da_args *args, struct xfs_trans_res *tres, unsigned int *total) { struct xfs_mount *mp = args->dp->i_mount; if (args->value) { 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 { *tres = M_RES(mp)->tr_attrrm; *total = XFS_ATTRRM_SPACE_RES(mp); } } /* * Add an attr to a shortform fork. If there is no space, * xfs_attr_shortform_addname() will convert to leaf format and return -ENOSPC. * to use. */ STATIC int xfs_attr_try_sf_addname( struct xfs_inode *dp, struct xfs_da_args *args) { int error; /* * Build initial attribute list (if required). */ if (dp->i_af.if_format == XFS_DINODE_FMT_EXTENTS) xfs_attr_shortform_create(args); 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->op_flags & XFS_DA_OP_NOTIME)) xfs_trans_ichgtime(args->trans, dp, XFS_ICHGTIME_CHG); if (xfs_has_wsync(dp->i_mount)) xfs_trans_set_sync(args->trans); return error; } static int xfs_attr_sf_addname( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; struct xfs_inode *dp = args->dp; int error = 0; error = xfs_attr_try_sf_addname(dp, args); if (error != -ENOSPC) { ASSERT(!error || error == -EEXIST); attr->xattri_dela_state = XFS_DAS_DONE; goto out; } /* * 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); if (error) return error; attr->xattri_dela_state = XFS_DAS_LEAF_ADD; out: trace_xfs_attr_sf_addname_return(attr->xattri_dela_state, args->dp); return error; } /* * Handle the state change on completion of a multi-state attr operation. * * If the XFS_DA_OP_REPLACE flag is set, this means the operation was the first * modification in a attr replace operation and we still have to do the second * state, indicated by @replace_state. * * We consume the XFS_DA_OP_REPLACE flag so that when we are called again on * completion of the second half of the attr replace operation we correctly * signal that it is done. */ static enum xfs_delattr_state xfs_attr_complete_op( struct xfs_attr_intent *attr, enum xfs_delattr_state replace_state) { struct xfs_da_args *args = attr->xattri_da_args; bool do_replace = args->op_flags & XFS_DA_OP_REPLACE; args->op_flags &= ~XFS_DA_OP_REPLACE; args->attr_filter &= ~XFS_ATTR_INCOMPLETE; if (do_replace) return replace_state; return XFS_DAS_DONE; } static int xfs_attr_leaf_addname( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; int error; ASSERT(xfs_attr_is_leaf(args->dp)); /* * Use the leaf buffer we may already hold locked as a result of * a sf-to-leaf conversion. */ error = xfs_attr_leaf_try_add(args); if (error == -ENOSPC) { error = xfs_attr3_leaf_to_node(args); if (error) return error; /* * We're not in leaf format anymore, so roll the transaction and * retry the add to the newly allocated node block. */ attr->xattri_dela_state = XFS_DAS_NODE_ADD; goto out; } if (error) return error; /* * We need to commit and roll if we need to allocate remote xattr blocks * or perform more xattr manipulations. Otherwise there is nothing more * to do and we can return success. */ if (args->rmtblkno) attr->xattri_dela_state = XFS_DAS_LEAF_SET_RMT; else attr->xattri_dela_state = xfs_attr_complete_op(attr, XFS_DAS_LEAF_REPLACE); out: trace_xfs_attr_leaf_addname_return(attr->xattri_dela_state, args->dp); return error; } /* * Add an entry to a node format attr tree. * * Note that we might still have a leaf here - xfs_attr_is_leaf() cannot tell * the difference between leaf + remote attr blocks and a node format tree, * so we may still end up having to convert from leaf to node format here. */ static int xfs_attr_node_addname( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; int error; error = xfs_attr_node_addname_find_attr(attr); if (error) return error; error = xfs_attr_node_try_addname(attr); if (error == -ENOSPC) { error = xfs_attr3_leaf_to_node(args); if (error) return error; /* * No state change, we really are in node form now * but we need the transaction rolled to continue. */ goto out; } if (error) return error; if (args->rmtblkno) attr->xattri_dela_state = XFS_DAS_NODE_SET_RMT; else attr->xattri_dela_state = xfs_attr_complete_op(attr, XFS_DAS_NODE_REPLACE); out: trace_xfs_attr_node_addname_return(attr->xattri_dela_state, args->dp); return error; } static int xfs_attr_rmtval_alloc( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; int error = 0; /* * 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 (attr->xattri_blkcnt > 0) { error = xfs_attr_rmtval_set_blk(attr); if (error) return error; /* Roll the transaction only if there is more to allocate. */ if (attr->xattri_blkcnt > 0) goto out; } error = xfs_attr_rmtval_set_value(args); if (error) return error; attr->xattri_dela_state = xfs_attr_complete_op(attr, ++attr->xattri_dela_state); /* * If we are not doing a rename, we've finished the operation but still * have to clear the incomplete flag protecting the new attr from * exposing partially initialised state if we crash during creation. */ if (attr->xattri_dela_state == XFS_DAS_DONE) error = xfs_attr3_leaf_clearflag(args); out: trace_xfs_attr_rmtval_alloc(attr->xattri_dela_state, args->dp); return error; } /* * Mark an attribute entry INCOMPLETE and save pointers to the relevant buffers * for later deletion of the entry. */ static int xfs_attr_leaf_mark_incomplete( struct xfs_da_args *args, struct xfs_da_state *state) { int error; /* * 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) return error; /* * Mark the attribute as INCOMPLETE */ return xfs_attr3_leaf_setflag(args); } /* Ensure the da state of an xattr deferred work item is ready to go. */ static inline void xfs_attr_item_init_da_state( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; if (!attr->xattri_da_state) attr->xattri_da_state = xfs_da_state_alloc(args); else xfs_da_state_reset(attr->xattri_da_state, args); } /* * Initial setup for xfs_attr_node_removename. Make sure the attr is there and * the blocks are valid. Attr keys with remote blocks will be marked * incomplete. */ static int xfs_attr_node_removename_setup( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; struct xfs_da_state *state; int error; xfs_attr_item_init_da_state(attr); error = xfs_attr_node_lookup(args, attr->xattri_da_state); if (error != -EEXIST) goto out; error = 0; state = attr->xattri_da_state; ASSERT(state->path.blk[state->path.active - 1].bp != NULL); ASSERT(state->path.blk[state->path.active - 1].magic == XFS_ATTR_LEAF_MAGIC); error = xfs_attr_leaf_mark_incomplete(args, state); if (error) goto out; if (args->rmtblkno > 0) error = xfs_attr_rmtval_invalidate(args); out: if (error) { xfs_da_state_free(attr->xattri_da_state); attr->xattri_da_state = NULL; } return error; } /* * Remove the original attr we have just replaced. This is dependent on the * original lookup and insert placing the old attr in args->blkno/args->index * and the new attr in args->blkno2/args->index2. */ static int xfs_attr_leaf_remove_attr( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; struct xfs_inode *dp = args->dp; struct xfs_buf *bp = NULL; int forkoff; int error; error = xfs_attr3_leaf_read(args->trans, args->dp, args->blkno, &bp); if (error) return error; xfs_attr3_leaf_remove(bp, args); forkoff = xfs_attr_shortform_allfit(bp, dp); if (forkoff) error = xfs_attr3_leaf_to_shortform(bp, args, forkoff); /* bp is gone due to xfs_da_shrink_inode */ return error; } /* * Shrink an attribute from leaf to shortform. Used by the node format remove * path when the node format collapses to a single block and so we have to check * if it can be collapsed further. */ static int xfs_attr_leaf_shrink( struct xfs_da_args *args) { struct xfs_inode *dp = args->dp; struct xfs_buf *bp; int forkoff; int error; if (!xfs_attr_is_leaf(dp)) return 0; error = xfs_attr3_leaf_read(args->trans, args->dp, 0, &bp); if (error) return error; forkoff = xfs_attr_shortform_allfit(bp, dp); if (forkoff) { error = xfs_attr3_leaf_to_shortform(bp, args, forkoff); /* bp is gone due to xfs_da_shrink_inode */ } else { xfs_trans_brelse(args->trans, bp); } return error; } /* * Run the attribute operation specified in @attr. * * This routine is meant to function as a delayed operation and will set the * state to XFS_DAS_DONE when the operation is complete. Calling functions will * need to handle this, and recall the function until either an error or * XFS_DAS_DONE is detected. */ int xfs_attr_set_iter( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; int error = 0; /* State machine switch */ next_state: switch (attr->xattri_dela_state) { case XFS_DAS_UNINIT: ASSERT(0); return -EFSCORRUPTED; case XFS_DAS_SF_ADD: return xfs_attr_sf_addname(attr); case XFS_DAS_LEAF_ADD: return xfs_attr_leaf_addname(attr); case XFS_DAS_NODE_ADD: return xfs_attr_node_addname(attr); case XFS_DAS_SF_REMOVE: error = xfs_attr_sf_removename(args); attr->xattri_dela_state = xfs_attr_complete_op(attr, xfs_attr_init_add_state(args)); break; case XFS_DAS_LEAF_REMOVE: error = xfs_attr_leaf_removename(args); attr->xattri_dela_state = xfs_attr_complete_op(attr, xfs_attr_init_add_state(args)); break; case XFS_DAS_NODE_REMOVE: error = xfs_attr_node_removename_setup(attr); if (error == -ENOATTR && (args->op_flags & XFS_DA_OP_RECOVERY)) { attr->xattri_dela_state = xfs_attr_complete_op(attr, xfs_attr_init_add_state(args)); error = 0; break; } if (error) return error; attr->xattri_dela_state = XFS_DAS_NODE_REMOVE_RMT; if (args->rmtblkno == 0) attr->xattri_dela_state++; break; case XFS_DAS_LEAF_SET_RMT: case XFS_DAS_NODE_SET_RMT: error = xfs_attr_rmtval_find_space(attr); if (error) return error; attr->xattri_dela_state++; fallthrough; case XFS_DAS_LEAF_ALLOC_RMT: case XFS_DAS_NODE_ALLOC_RMT: error = xfs_attr_rmtval_alloc(attr); if (error) return error; if (attr->xattri_dela_state == XFS_DAS_DONE) break; goto next_state; case XFS_DAS_LEAF_REPLACE: case XFS_DAS_NODE_REPLACE: /* * We must "flip" the incomplete flags on the "new" and "old" * attribute/value pairs so that one disappears and one appears * atomically. */ error = xfs_attr3_leaf_flipflags(args); if (error) return error; /* * We must commit the flag value change now to make it atomic * and then we can start the next trans in series at REMOVE_OLD. */ attr->xattri_dela_state++; break; case XFS_DAS_LEAF_REMOVE_OLD: case XFS_DAS_NODE_REMOVE_OLD: /* * If we have a remote attr, start the process of removing it * by invalidating any cached buffers. * * If we don't have a remote attr, we skip the remote block * removal state altogether with a second state increment. */ xfs_attr_restore_rmt_blk(args); if (args->rmtblkno) { error = xfs_attr_rmtval_invalidate(args); if (error) return error; } else { attr->xattri_dela_state++; } attr->xattri_dela_state++; goto next_state; case XFS_DAS_LEAF_REMOVE_RMT: case XFS_DAS_NODE_REMOVE_RMT: error = xfs_attr_rmtval_remove(attr); if (error == -EAGAIN) { error = 0; break; } if (error) return error; /* * We've finished removing the remote attr blocks, so commit the * transaction and move on to removing the attr name from the * leaf/node block. Removing the attr might require a full * transaction reservation for btree block freeing, so we * can't do that in the same transaction where we removed the * remote attr blocks. */ attr->xattri_dela_state++; break; case XFS_DAS_LEAF_REMOVE_ATTR: error = xfs_attr_leaf_remove_attr(attr); attr->xattri_dela_state = xfs_attr_complete_op(attr, xfs_attr_init_add_state(args)); break; case XFS_DAS_NODE_REMOVE_ATTR: error = xfs_attr_node_remove_attr(attr); if (!error) error = xfs_attr_leaf_shrink(args); attr->xattri_dela_state = xfs_attr_complete_op(attr, xfs_attr_init_add_state(args)); break; default: ASSERT(0); break; } trace_xfs_attr_set_iter_return(attr->xattri_dela_state, args->dp); return error; } /* * Return EEXIST if attr is found, or ENOATTR if not */ static int xfs_attr_lookup( struct xfs_da_args *args) { struct xfs_inode *dp = args->dp; struct xfs_buf *bp = NULL; struct xfs_da_state *state; int error; if (!xfs_inode_hasattr(dp)) return -ENOATTR; if (dp->i_af.if_format == XFS_DINODE_FMT_LOCAL) { if (xfs_attr_sf_findname(args)) return -EEXIST; return -ENOATTR; } if (xfs_attr_is_leaf(dp)) { error = xfs_attr_leaf_hasname(args, &bp); if (bp) xfs_trans_brelse(args->trans, bp); return error; } state = xfs_da_state_alloc(args); error = xfs_attr_node_lookup(args, state); xfs_da_state_free(state); return error; } static void xfs_attr_defer_add( struct xfs_da_args *args, unsigned int op_flags) { struct xfs_attr_intent *new; new = kmem_cache_zalloc(xfs_attr_intent_cache, GFP_KERNEL | __GFP_NOFAIL); new->xattri_op_flags = op_flags; new->xattri_da_args = args; switch (op_flags) { case XFS_ATTRI_OP_FLAGS_SET: new->xattri_dela_state = xfs_attr_init_add_state(args); break; case XFS_ATTRI_OP_FLAGS_REPLACE: new->xattri_dela_state = xfs_attr_init_replace_state(args); break; case XFS_ATTRI_OP_FLAGS_REMOVE: new->xattri_dela_state = xfs_attr_init_remove_state(args); break; default: ASSERT(0); } xfs_defer_add(args->trans, &new->xattri_list, &xfs_attr_defer_type); trace_xfs_attr_defer_add(new->xattri_dela_state, args->dp); } /* * 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; bool rsvd = (args->attr_filter & XFS_ATTR_ROOT); int error, local; int rmt_blks = 0; unsigned int total; if (xfs_is_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. Preserve the logged flag, since we need * to pass that through to the logging code. */ args->op_flags = XFS_DA_OP_OKNOENT | (args->op_flags & XFS_DA_OP_LOGGED); if (args->value) { XFS_STATS_INC(mp, xs_attr_set); 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_inode_has_attr_fork(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; } if (!local) rmt_blks = xfs_attr3_rmt_blocks(mp, args->valuelen); } else { XFS_STATS_INC(mp, xs_attr_remove); rmt_blks = xfs_attr3_rmt_blocks(mp, XFS_XATTR_SIZE_MAX); } /* * Root fork attributes can use reserved data blocks for this * operation if necessary */ xfs_init_attr_trans(args, &tres, &total); error = xfs_trans_alloc_inode(dp, &tres, total, 0, rsvd, &args->trans); if (error) return error; if (args->value || xfs_inode_hasattr(dp)) { error = xfs_iext_count_may_overflow(dp, XFS_ATTR_FORK, XFS_IEXT_ATTR_MANIP_CNT(rmt_blks)); if (error == -EFBIG) error = xfs_iext_count_upgrade(args->trans, dp, XFS_IEXT_ATTR_MANIP_CNT(rmt_blks)); if (error) goto out_trans_cancel; } error = xfs_attr_lookup(args); switch (error) { case -EEXIST: if (!args->value) { /* if no value, we are performing a remove operation */ xfs_attr_defer_add(args, XFS_ATTRI_OP_FLAGS_REMOVE); break; } /* Pure create fails if the attr already exists */ if (args->attr_flags & XATTR_CREATE) goto out_trans_cancel; xfs_attr_defer_add(args, XFS_ATTRI_OP_FLAGS_REPLACE); break; case -ENOATTR: /* Can't remove what isn't there. */ if (!args->value) goto out_trans_cancel; /* Pure replace fails if no existing attr to replace. */ if (args->attr_flags & XATTR_REPLACE) goto out_trans_cancel; xfs_attr_defer_add(args, XFS_ATTRI_OP_FLAGS_SET); break; default: goto out_trans_cancel; } /* * If this is a synchronous mount, make sure that the * transaction goes to disk before returning to the user. */ if (xfs_has_wsync(mp)) xfs_trans_set_sync(args->trans); if (!(args->op_flags & XFS_DA_OP_NOTIME)) 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 *========================================================================*/ static inline int xfs_attr_sf_totsize(struct xfs_inode *dp) { struct xfs_attr_sf_hdr *sf = dp->i_af.if_data; return be16_to_cpu(sf->totsize); } /* * Add a name to the shortform attribute list structure * This is the external routine. */ static int xfs_attr_shortform_addname( struct xfs_da_args *args) { int newsize, forkoff; trace_xfs_attr_sf_addname(args); if (xfs_attr_sf_findname(args)) { int error; ASSERT(args->op_flags & XFS_DA_OP_REPLACE); error = xfs_attr_sf_removename(args); if (error) return error; /* * Since we have removed the old attr, clear XFS_DA_OP_REPLACE * so that the new attr doesn't fit in shortform format, the * leaf format add routine won't trip over the attr not being * around. */ args->op_flags &= ~XFS_DA_OP_REPLACE; } else { ASSERT(!(args->op_flags & XFS_DA_OP_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 *========================================================================*/ /* Save the current remote block info and clear the current pointers. */ static void xfs_attr_save_rmt_blk( struct xfs_da_args *args) { args->blkno2 = args->blkno; args->index2 = args->index; args->rmtblkno2 = args->rmtblkno; args->rmtblkcnt2 = args->rmtblkcnt; args->rmtvaluelen2 = args->rmtvaluelen; args->rmtblkno = 0; args->rmtblkcnt = 0; args->rmtvaluelen = 0; } /* Set stored info about a remote block */ static void xfs_attr_restore_rmt_blk( struct xfs_da_args *args) { args->blkno = args->blkno2; args->index = args->index2; args->rmtblkno = args->rmtblkno2; args->rmtblkcnt = args->rmtblkcnt2; args->rmtvaluelen = args->rmtvaluelen2; } /* * Tries to add an attribute to an inode in leaf form * * This function is meant to execute as part of a delayed operation and leaves * the transaction handling to the caller. On success the attribute is added * and the inode and transaction are left dirty. If there is not enough space, * the attr data is converted to node format and -ENOSPC is returned. Caller is * responsible for handling the dirty inode and transaction or adding the attr * in node format. */ STATIC int xfs_attr_leaf_try_add( struct xfs_da_args *args) { struct xfs_buf *bp; int error; error = xfs_attr3_leaf_read(args->trans, args->dp, 0, &bp); if (error) return error; /* * Look up the xattr name to set the insertion point for the new xattr. */ error = xfs_attr3_leaf_lookup_int(bp, args); switch (error) { case -ENOATTR: if (args->op_flags & XFS_DA_OP_REPLACE) goto out_brelse; break; case -EEXIST: if (!(args->op_flags & XFS_DA_OP_REPLACE)) goto out_brelse; trace_xfs_attr_leaf_replace(args); /* * Save the existing remote attr state so that the current * values reflect the state of the new attribute we are about to * add, not the attribute we just found and will remove later. */ xfs_attr_save_rmt_blk(args); break; case 0: break; default: goto out_brelse; } return xfs_attr3_leaf_add(bp, args); out_brelse: xfs_trans_brelse(args->trans, bp); return error; } /* * Return EEXIST if attr is found, or ENOATTR if not */ STATIC int xfs_attr_leaf_hasname( struct xfs_da_args *args, struct xfs_buf **bp) { int error = 0; error = xfs_attr3_leaf_read(args->trans, args->dp, 0, bp); if (error) return error; error = xfs_attr3_leaf_lookup_int(*bp, args); if (error != -ENOATTR && error != -EEXIST) xfs_trans_brelse(args->trans, *bp); 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; error = xfs_attr_leaf_hasname(args, &bp); if (error == -ENOATTR) { xfs_trans_brelse(args->trans, bp); if (args->op_flags & XFS_DA_OP_RECOVERY) return 0; return error; } else if (error != -EEXIST) return error; xfs_attr3_leaf_remove(bp, args); /* * If the result is small enough, shrink it all into the inode. */ forkoff = xfs_attr_shortform_allfit(bp, dp); if (forkoff) return xfs_attr3_leaf_to_shortform(bp, args, forkoff); /* bp is gone due to xfs_da_shrink_inode */ 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); error = xfs_attr_leaf_hasname(args, &bp); if (error == -ENOATTR) { xfs_trans_brelse(args->trans, bp); return error; } else if (error != -EEXIST) return error; error = xfs_attr3_leaf_getvalue(bp, args); xfs_trans_brelse(args->trans, bp); return error; } /* Return EEXIST if attr is found, or ENOATTR if not. */ STATIC int xfs_attr_node_lookup( struct xfs_da_args *args, struct xfs_da_state *state) { int retval, error; /* * Search to see if name exists, and get back a pointer to it. */ error = xfs_da3_node_lookup_int(state, &retval); if (error) return error; return retval; } /*======================================================================== * External routines when attribute list size > geo->blksize *========================================================================*/ STATIC int xfs_attr_node_addname_find_attr( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; int error; /* * Search to see if name already exists, and get back a pointer * to where it should go. */ xfs_attr_item_init_da_state(attr); error = xfs_attr_node_lookup(args, attr->xattri_da_state); switch (error) { case -ENOATTR: if (args->op_flags & XFS_DA_OP_REPLACE) goto error; break; case -EEXIST: if (!(args->op_flags & XFS_DA_OP_REPLACE)) goto error; trace_xfs_attr_node_replace(args); /* * Save the existing remote attr state so that the current * values reflect the state of the new attribute we are about to * add, not the attribute we just found and will remove later. */ xfs_attr_save_rmt_blk(args); break; case 0: break; default: goto error; } return 0; error: if (attr->xattri_da_state) { xfs_da_state_free(attr->xattri_da_state); attr->xattri_da_state = NULL; } return error; } /* * 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). */ static int xfs_attr_node_try_addname( struct xfs_attr_intent *attr) { struct xfs_da_state *state = attr->xattri_da_state; struct xfs_da_state_blk *blk; int error; trace_xfs_attr_node_addname(state->args); blk = &state->path.blk[state->path.active-1]; ASSERT(blk->magic == XFS_ATTR_LEAF_MAGIC); error = xfs_attr3_leaf_add(blk->bp, state->args); if (error == -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. Let the caller deal with this. */ goto out; } /* * 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; } else { /* * Addition succeeded, update Btree hashvals. */ xfs_da3_fixhashpath(state, &state->path); } out: xfs_da_state_free(state); attr->xattri_da_state = NULL; return error; } static int xfs_attr_node_removename( struct xfs_da_args *args, struct xfs_da_state *state) { struct xfs_da_state_blk *blk; int retval; /* * 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); return retval; } static int xfs_attr_node_remove_attr( struct xfs_attr_intent *attr) { struct xfs_da_args *args = attr->xattri_da_args; struct xfs_da_state *state = xfs_da_state_alloc(args); int retval = 0; int error = 0; /* * The attr we are removing has already been marked incomplete, so * we need to set the filter appropriately to re-find the "old" * attribute entry after any split ops. */ args->attr_filter |= XFS_ATTR_INCOMPLETE; error = xfs_da3_node_lookup_int(state, &retval); if (error) goto out; error = xfs_attr_node_removename(args, state); /* * 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; } retval = error = 0; out: xfs_da_state_free(state); if (error) return error; return retval; } /* * 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( struct xfs_da_args *args) { struct xfs_da_state *state; struct xfs_da_state_blk *blk; int i; int error; trace_xfs_attr_node_get(args); /* * Search to see if name exists, and get back a pointer to it. */ state = xfs_da_state_alloc(args); error = xfs_attr_node_lookup(args, state); if (error != -EEXIST) goto out_release; /* * Get the value, local or "remote" */ blk = &state->path.blk[state->path.active - 1]; error = 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 error; } /* 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); } int __init xfs_attr_intent_init_cache(void) { xfs_attr_intent_cache = kmem_cache_create("xfs_attr_intent", sizeof(struct xfs_attr_intent), 0, 0, NULL); return xfs_attr_intent_cache != NULL ? 0 : -ENOMEM; } void xfs_attr_intent_destroy_cache(void) { kmem_cache_destroy(xfs_attr_intent_cache); xfs_attr_intent_cache = NULL; }