*/
- error = xfs_trans_roll_inode(&args->trans, dp);
- if (error)
- return error;
+ dac->flags |= XFS_DAC_DEFER_FINISH;
+ return -EAGAIN;
+ case 0:
+ dac->dela_state = XFS_DAS_FOUND_LBLK;
+ return -EAGAIN;
}
-
- error = xfs_attr_node_addname(args);
return error;
}
@@ -728,28 +789,30 @@ xfs_attr_leaf_try_add(
*
* 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).
+ *
+ * This routine is meant to function as a delayed operation, and may return
+ * -EAGAIN when the transaction needs to be rolled. Calling functions will need
+ * to handle this, and recall the function until a successful error code is
+ * returned.
*/
STATIC int
xfs_attr_leaf_addname(
- struct xfs_da_args *args)
+ struct xfs_delattr_context *dac)
{
- int error, forkoff;
- struct xfs_buf *bp = NULL;
- struct xfs_inode *dp = args->dp;
-
- trace_xfs_attr_leaf_addname(args);
-
- error = xfs_attr_leaf_try_add(args, bp);
- if (error)
- return error;
+ struct xfs_da_args *args = dac->da_args;
+ struct xfs_buf *bp = NULL;
+ int error, forkoff;
+ struct xfs_inode *dp = args->dp;
- /*
- * 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;
+ /* State machine switch */
+ switch (dac->dela_state) {
+ case XFS_DAS_FLIP_LFLAG:
+ goto das_flip_flag;
+ case XFS_DAS_RM_LBLK:
+ goto das_rm_lblk;
+ default:
+ break;
+ }
/*
* If there was an out-of-line value, allocate the blocks we
@@ -757,12 +820,34 @@ xfs_attr_leaf_addname(
* 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);
+
+ /* Open coded xfs_attr_rmtval_set without trans handling */
+ if ((dac->flags & XFS_DAC_LEAF_ADDNAME_INIT) == 0) {
+ dac->flags |= XFS_DAC_LEAF_ADDNAME_INIT;
+ if (args->rmtblkno > 0) {
+ error = xfs_attr_rmtval_find_space(dac);
+ if (error)
+ return error;
+ }
+ }
+
+ /*
+ * Roll through the "value", allocating blocks on disk as
+ * required.
+ */
+ if (dac->blkcnt > 0) {
+ error = xfs_attr_rmtval_set_blk(dac);
if (error)
return error;
+
+ dac->flags |= XFS_DAC_DEFER_FINISH;
+ return -EAGAIN;
}
+ error = xfs_attr_rmtval_set_value(args);
+ if (error)
+ return error;
+
if (!(args->op_flags & XFS_DA_OP_RENAME)) {
/*
* Added a "remote" value, just clear the incomplete flag.
@@ -782,29 +867,30 @@ xfs_attr_leaf_addname(
* 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;
/*
* Commit the flag value change and start the next trans in series.
*/
- error = xfs_trans_roll_inode(&args->trans, args->dp);
- if (error)
- return error;
-
+ dac->dela_state = XFS_DAS_FLIP_LFLAG;
+ return -EAGAIN;
+das_flip_flag:
/*
* Dismantle the "old" attribute/value pair by removing a "remote" value
* (if it exists).
*/
xfs_attr_restore_rmt_blk(args);
- if (args->rmtblkno) {
- error = xfs_attr_rmtval_invalidate(args);
- if (error)
- return error;
+ error = xfs_attr_rmtval_invalidate(args);
+ if (error)
+ return error;
- error = xfs_attr_rmtval_remove(args);
+ /* Set state in case xfs_attr_rmtval_remove returns -EAGAIN */
+ dac->dela_state = XFS_DAS_RM_LBLK;
+das_rm_lblk:
+ if (args->rmtblkno) {
+ error = __xfs_attr_rmtval_remove(dac);
if (error)
return error;
}
@@ -970,23 +1056,38 @@ xfs_attr_node_hasname(
*
* "Remote" attribute values confuse the issue and atomic rename operations
* add a whole extra layer of confusion on top of that.
+ *
+ * This routine is meant to function as a delayed operation, and may return
+ * -EAGAIN when the transaction needs to be rolled. Calling functions will need
+ * to handle this, and recall the function until a successful error code is
+ *returned.
*/
STATIC int
xfs_attr_node_addname(
- struct xfs_da_args *args)
+ struct xfs_delattr_context *dac)
{
- struct xfs_da_state *state;
- struct xfs_da_state_blk *blk;
- struct xfs_inode *dp;
- int retval, error;
+ struct xfs_da_args *args = dac->da_args;
+ struct xfs_da_state *state = NULL;
+ struct xfs_da_state_blk *blk;
+ int retval = 0;
+ int error = 0;
trace_xfs_attr_node_addname(args);
- /*
- * Fill in bucket of arguments/results/context to carry around.
- */
- dp = args->dp;
-restart:
+ /* State machine switch */
+ switch (dac->dela_state) {
+ case XFS_DAS_FLIP_NFLAG:
+ goto das_flip_flag;
+ case XFS_DAS_FOUND_NBLK:
+ goto das_found_nblk;
+ case XFS_DAS_ALLOC_NODE:
+ goto das_alloc_node;
+ case XFS_DAS_RM_NBLK:
+ goto das_rm_nblk;
+ default:
+ break;
+ }
+
/*
* Search to see if name already exists, and get back a pointer
* to where it should go.
@@ -1032,19 +1133,16 @@ xfs_attr_node_addname(
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.
+ * Now that we have converted the leaf to a node, we can
+ * roll the transaction, and try xfs_attr3_leaf_add
+ * again on re-entry. No need to set dela_state to do
+ * this. dela_state is still unset by this function at
+ * this point.
*/
- error = xfs_trans_roll_inode(&args->trans, dp);
- if (error)
- goto out;
-
- goto restart;
+ dac->flags |= XFS_DAC_DEFER_FINISH;
+ return -EAGAIN;
}
/*
@@ -1056,9 +1154,7 @@ xfs_attr_node_addname(
error = xfs_da3_split(state);
if (error)
goto out;
- error = xfs_defer_finish(&args->trans);
- if (error)
- goto out;
+ dac->flags |= XFS_DAC_DEFER_FINISH;
} else {
/*
* Addition succeeded, update Btree hashvals.
@@ -1066,6 +1162,11 @@ xfs_attr_node_addname(
xfs_da3_fixhashpath(state, &state->path);
}
+ if (!args->rmtblkno && !(args->op_flags & XFS_DA_OP_RENAME)) {
+ retval = error;
+ goto out;
+ }
+
/*
* Kill the state structure, we're done with it and need to
* allow the buffers to come back later.
@@ -1073,13 +1174,9 @@ xfs_attr_node_addname(
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;
+ dac->dela_state = XFS_DAS_FOUND_NBLK;
+ return -EAGAIN;
+das_found_nblk:
/*
* If there was an out-of-line value, allocate the blocks we
@@ -1088,7 +1185,27 @@ xfs_attr_node_addname(
* maximum size of a transaction and/or hit a deadlock.
*/
if (args->rmtblkno > 0) {
- error = xfs_attr_rmtval_set(args);
+ /* Open coded xfs_attr_rmtval_set without trans handling */
+ error = xfs_attr_rmtval_find_space(dac);
+ if (error)
+ return error;
+
+ /*
+ * Roll through the "value", allocating blocks on disk as
+ * required. Set the state in case of -EAGAIN return code
+ */
+ dac->dela_state = XFS_DAS_ALLOC_NODE;
+das_alloc_node:
+ if (dac->blkcnt > 0) {
+ error = xfs_attr_rmtval_set_blk(dac);
+ if (error)
+ return error;
+
+ dac->flags |= XFS_DAC_DEFER_FINISH;
+ return -EAGAIN;
+ }
+
+ error = xfs_attr_rmtval_set_value(args);
if (error)
return error;
}
@@ -1118,22 +1235,24 @@ xfs_attr_node_addname(
/*
* Commit the flag value change and start the next trans in series
*/
- error = xfs_trans_roll_inode(&args->trans, args->dp);
- if (error)
- goto out;
-
+ dac->dela_state = XFS_DAS_FLIP_NFLAG;
+ return -EAGAIN;
+das_flip_flag:
/*
* Dismantle the "old" attribute/value pair by removing a "remote" value
* (if it exists).
*/
xfs_attr_restore_rmt_blk(args);
- if (args->rmtblkno) {
- error = xfs_attr_rmtval_invalidate(args);
- if (error)
- return error;
+ error = xfs_attr_rmtval_invalidate(args);
+ if (error)
+ return error;
- error = xfs_attr_rmtval_remove(args);
+ /* Set state in case xfs_attr_rmtval_remove returns -EAGAIN */
+ dac->dela_state = XFS_DAS_RM_NBLK;
+das_rm_nblk:
+ if (args->rmtblkno) {
+ error = __xfs_attr_rmtval_remove(dac);
if (error)
return error;
}
diff --git a/fs/xfs/libxfs/xfs_attr.h b/fs/xfs/libxfs/xfs_attr.h
index 3154ef4..e101238 100644
--- a/fs/xfs/libxfs/xfs_attr.h
+++ b/fs/xfs/libxfs/xfs_attr.h
@@ -135,6 +135,227 @@ struct xfs_attr_list_context {
* v
* done
*
+ *
+ * Below is a state machine diagram for attr set operations.
+ *
+ * It seems the challenge with undertanding this system comes from trying to
+ * absorb the state machine all at once, when really one should only be looking
+ * at it with in the context of a single function. Once a state sensitive
+ * function is called, the idea is that it "takes ownership" of the
+ * statemachine. It isn't concerned with the states that may have belonged to
+ * it's calling parent. Only the states relevant to itself or any other
+ * subroutines there in. Once a calling function hands off the statemachine to
+ * a subroutine, it needs to respect the simple rule that it doesn't "own" the
+ * statemachine anymore, and it's the responsibility of that calling function to
+ * propagate the -EAGAIN back up the call stack. Upon reentry, it is committed
+ * to re-calling that subroutine until it returns something other than -EAGAIN.
+ * Once that subroutine signals completion (by returning anything other than
+ * -EAGAIN), the calling function can resume using the statemachine.
+ *
+ * xfs_attr_set_iter()
+ * │
+ * v
+ * ┌─y─ has an attr fork?
+ * │ |
+ * │ n
+ * │ |
+ * │ V
+ * │ add a fork
+ * │ │
+ * └──────────┤
+ * │
+ * V
+ * ┌─n── is shortform?
+ * │ |
+ * │ y
+ * │ |
+ * │ V
+ * │ xfs_attr_set_shortform
+ * │ |
+ * │ V
+ * │ had enough ──y──> done
+ * │ space?
+ * │ │
+ * │ n
+ * │ │
+ * │ V
+ * │ return -EAGAIN
+ * │ Re-enter in leaf form
+ * │ │
+ * └──────────┤
+ * │
+ * V
+ * release leaf buffer
+ * if needed
+ * │
+ * V
+ * ┌───n── fork has
+ * │ only 1 blk?
+ * │ │
+ * │ y
+ * │ │
+ * │ v
+ * │ xfs_attr_leaf_try_add()
+ * │ │
+ * │ v
+ * │ had enough
+ * │ ┌────n── space?
+ * │ │ │
+ * │ v │
+ * │ return -EAGAIN │
+ * │ re-enter in y
+ * │ node form │
+ * │ │ │
+ * ├───────┘ │
+ * │ v
+ * │ XFS_DAS_FOUND_LBLK ──┐
+ * │ │
+ * │ XFS_DAS_FLIP_LFLAG ──┤
+ * │ (subroutine state) │
+ * │ │
+ * │ └─>xfs_attr_leaf_addname()
+ * │ │
+ * │ v
+ * │ ┌──first time through?
+ * │ │ │
+ * │ │ y
+ * │ │ │
+ * │ n v
+ * │ │ if we have rmt blks
+ * │ │ find space for them
+ * │ │ │
+ * │ └──────────┤
+ * │ │
+ * │ v
+ * │ still have
+ * │ ┌─n─ blks to alloc? <──┐
+ * │ │ │ │
+ * │ │ y │
+ * │ │ │ │
+ * │ │ v │
+ * │ │ alloc one blk │
+ * │ │ return -EAGAIN ──┘
+ * │ │ re-enter with one
+ * │ │ less blk to alloc
+ * │ │
+ * │ │
+ * │ └───> set the rmt
+ * │ value
+ * │ │
+ * │ v
+ * │ was this
+ * │ a rename? ──n─┐
+ * │ │ │
+ * │ y │
+ * │ │ │
+ * │ v │
+ * │ flip incomplete │
+ * │ flag │
+ * │ │ │
+ * │ v │
+ * │ XFS_DAS_FLIP_LFLAG │
+ * │ │ │
+ * │ v │
+ * │ remove │
+ * │ XFS_DAS_RM_LBLK ─> old name │
+ * │ ^ │ │
+ * │ │ v │
+ * │ └──────y── more to │
+ * │ remove │
+ * │ │ │
+ * │ n │
+ * │ │ │
+ * │ v │
+ * │ done <──────┘
+ * └──> XFS_DAS_FOUND_NBLK ──┐
+ * (subroutine state) │
+ * │
+ * XFS_DAS_ALLOC_NODE ──┤
+ * (subroutine state) │
+ * │
+ * XFS_DAS_FLIP_NFLAG ──┤
+ * (subroutine state) │
+ * │
+ * └─>xfs_attr_node_addname()
+ * │
+ * v
+ * determine if this
+ * is create or rename
+ * find space to store attr
+ * │
+ * v
+ * ┌──────n──── fits in a node leaf?
+ * │ ^ │
+ * single leaf node? │ │
+ * │ │ │ y
+ * n y │ │
+ * │ │ │ v
+ * v v │ update
+ * split if grow the leaf ─┘ hashvals
+ * needed return -EAGAIN │
+ * │ retry leaf add │
+ * │ on reentry │
+ * │ │
+ * └───────────────────────────┤
+ * v
+ * need to alloc ──n──> done
+ * or flip flag?
+ * │
+ * y
+ * │
+ * v
+ * XFS_DAS_FOUND_NBLK
+ * │
+ * v
+ * ┌─────n── need to
+ * │ alloc blks?
+ * │ │
+ * │ y
+ * │ │
+ * │ v
+ * │ find space
+ * │ │
+ * │ v
+ * │ ┌─>XFS_DAS_ALLOC_NODE
+ * │ │ │
+ * │ │ v
+ * │ │ alloc blk
+ * │ │ │
+ * │ │ v
+ * │ └──y── need to alloc
+ * │ more blocks?
+ * │ │
+ * │ n
+ * │ │
+ * │ v
+ * │ set the rmt value
+ * │ │
+ * │ v
+ * │ was this
+ * └────────> a rename? ──n─┐
+ * │ │
+ * y │
+ * │ │
+ * v │
+ * flip incomplete │
+ * flag │
+ * │ │
+ * v │
+ * XFS_DAS_FLIP_NFLAG │
+ * │ │
+ * v │
+ * remove │
+ * XFS_DAS_RM_NBLK ─> old name │
+ * ^ │ │
+ * │ v │
+ * └──────y── more to │
+ * remove │
+ * │ │
+ * n │
+ * │ │
+ * v │
+ * done <──────┘
+ *
*/
/*
@@ -149,12 +370,20 @@ struct xfs_attr_list_context {
enum xfs_delattr_state {
XFS_DAS_UNINIT = 0, /* No state has been set yet */
XFS_DAS_RM_SHRINK, /* We are shrinking the tree */
+ XFS_DAS_FOUND_LBLK, /* We found leaf blk for attr */
+ XFS_DAS_FOUND_NBLK, /* We found node blk for attr */
+ XFS_DAS_FLIP_LFLAG, /* Flipped leaf INCOMPLETE attr flag */
+ XFS_DAS_RM_LBLK, /* A rename is removing leaf blocks */
+ XFS_DAS_ALLOC_NODE, /* We are allocating node blocks */
+ XFS_DAS_FLIP_NFLAG, /* Flipped node INCOMPLETE attr flag */
+ XFS_DAS_RM_NBLK, /* A rename is removing node blocks */
};
/*
* Defines for xfs_delattr_context.flags
*/
#define XFS_DAC_DEFER_FINISH 0x01 /* finish the transaction */
+#define XFS_DAC_LEAF_ADDNAME_INIT 0x02 /* xfs_attr_leaf_addname init*/
/*
* Context used for keeping track of delayed attribute operations
@@ -162,6 +391,11 @@ enum xfs_delattr_state {
struct xfs_delattr_context {
struct xfs_da_args *da_args;
+ /* Used in xfs_attr_rmtval_set_blk to roll through allocating blocks */
+ struct xfs_bmbt_irec map;
+ xfs_dablk_t lblkno;
+ int blkcnt;
+
/* Used in xfs_attr_node_removename to roll through removing blocks */
struct xfs_da_state *da_state;
@@ -188,7 +422,6 @@ int xfs_attr_set_args(struct xfs_da_args *args);
int xfs_has_attr(struct xfs_da_args *args);
int xfs_attr_remove_args(struct xfs_da_args *args);
int xfs_attr_remove_iter(struct xfs_delattr_context *dac);
-int xfs_attr_trans_roll(struct xfs_delattr_context *dac);
bool xfs_attr_namecheck(const void *name, size_t length);
void xfs_delattr_context_init(struct xfs_delattr_context *dac,
struct xfs_da_args *args);
diff --git a/fs/xfs/libxfs/xfs_attr_remote.c b/fs/xfs/libxfs/xfs_attr_remote.c
index f09820c..6af86bf 100644
--- a/fs/xfs/libxfs/xfs_attr_remote.c
+++ b/fs/xfs/libxfs/xfs_attr_remote.c
@@ -441,7 +441,7 @@ xfs_attr_rmtval_get(
* Find a "hole" in the attribute address space large enough for us to drop the
* new attribute's value into
*/
-STATIC int
+int
xfs_attr_rmt_find_hole(
struct xfs_da_args *args)
{
@@ -468,7 +468,7 @@ xfs_attr_rmt_find_hole(
return 0;
}
-STATIC int
+int
xfs_attr_rmtval_set_value(
struct xfs_da_args *args)
{
@@ -628,6 +628,69 @@ xfs_attr_rmtval_set(
}
/*
+ * Find a hole for the attr and store it in the delayed attr context. This
+ * initializes the context to roll through allocating an attr extent for a
+ * delayed attr operation
+ */
+int
+xfs_attr_rmtval_find_space(
+ struct xfs_delattr_context *dac)
+{
+ struct xfs_da_args *args = dac->da_args;
+ struct xfs_bmbt_irec *map = &dac->map;
+ int error;
+
+ dac->lblkno = 0;
+ dac->blkcnt = 0;
+ args->rmtblkcnt = 0;
+ args->rmtblkno = 0;
+ memset(map, 0, sizeof(struct xfs_bmbt_irec));
+
+ error = xfs_attr_rmt_find_hole(args);
+ if (error)
+ return error;
+
+ dac->blkcnt = args->rmtblkcnt;
+ dac->lblkno = args->rmtblkno;
+
+ return 0;
+}
+
+/*
+ * Write one block of the value associated with an attribute into the
+ * out-of-line buffer that we have defined for it. This is similar to a subset
+ * of xfs_attr_rmtval_set, but records the current block to the delayed attr
+ * context, and leaves transaction handling to the caller.
+ */
+int
+xfs_attr_rmtval_set_blk(
+ struct xfs_delattr_context *dac)
+{
+ struct xfs_da_args *args = dac->da_args;
+ struct xfs_inode *dp = args->dp;
+ struct xfs_bmbt_irec *map = &dac->map;
+ int nmap;
+ int error;
+
+ nmap = 1;
+ error = xfs_bmapi_write(args->trans, dp, (xfs_fileoff_t)dac->lblkno,
+ dac->blkcnt, XFS_BMAPI_ATTRFORK, args->total,
+ map, &nmap);
+ if (error)
+ return error;
+
+ ASSERT(nmap == 1);
+ ASSERT((map->br_startblock != DELAYSTARTBLOCK) &&
+ (map->br_startblock != HOLESTARTBLOCK));
+
+ /* roll attribute extent map forwards */
+ dac->lblkno += map->br_blockcount;
+ dac->blkcnt -= map->br_blockcount;
+
+ return 0;
+}
+
+/*
* Remove the value associated with an attribute by deleting the
* out-of-line buffer that it is stored on.
*/
@@ -669,37 +732,6 @@ xfs_attr_rmtval_invalidate(
}
/*
- * Remove the value associated with an attribute by deleting the
- * out-of-line buffer that it is stored on.
- */
-int
-xfs_attr_rmtval_remove(
- struct xfs_da_args *args)
-{
- int error;
- struct xfs_delattr_context dac = {
- .da_args = args,
- };
-
- trace_xfs_attr_rmtval_remove(args);
-
- /*
- * Keep de-allocating extents until the remote-value region is gone.
- */
- do {
- error = __xfs_attr_rmtval_remove(&dac);
- if (error != -EAGAIN)
- break;
-
- error = xfs_attr_trans_roll(&dac);
- if (error)
- return error;
- } while (true);
-
- return error;
-}
-
-/*
* Remove the value associated with an attribute by deleting the out-of-line
* buffer that it is stored on. Returns -EAGAIN for the caller to refresh the
* transaction and re-call the function
diff --git a/fs/xfs/libxfs/xfs_attr_remote.h b/fs/xfs/libxfs/xfs_attr_remote.h
index 002fd30..8ad68d5 100644
--- a/fs/xfs/libxfs/xfs_attr_remote.h
+++ b/fs/xfs/libxfs/xfs_attr_remote.h
@@ -10,9 +10,12 @@ int xfs_attr3_rmt_blocks(struct xfs_mount *mp, int attrlen);
int xfs_attr_rmtval_get(struct xfs_da_args *args);
int xfs_attr_rmtval_set(struct xfs_da_args *args);
-int xfs_attr_rmtval_remove(struct xfs_da_args *args);
int xfs_attr_rmtval_stale(struct xfs_inode *ip, struct xfs_bmbt_irec *map,
xfs_buf_flags_t incore_flags);
int xfs_attr_rmtval_invalidate(struct xfs_da_args *args);
int __xfs_attr_rmtval_remove(struct xfs_delattr_context *dac);
+int xfs_attr_rmt_find_hole(struct xfs_da_args *args);
+int xfs_attr_rmtval_set_value(struct xfs_da_args *args);
+int xfs_attr_rmtval_set_blk(struct xfs_delattr_context *dac);
+int xfs_attr_rmtval_find_space(struct xfs_delattr_context *dac);
#endif /* __XFS_ATTR_REMOTE_H__ */
diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h
index 5a263ae..9074b8b 100644
--- a/fs/xfs/xfs_trace.h
+++ b/fs/xfs/xfs_trace.h
@@ -1943,7 +1943,6 @@ DEFINE_ATTR_EVENT(xfs_attr_refillstate);
DEFINE_ATTR_EVENT(xfs_attr_rmtval_get);
DEFINE_ATTR_EVENT(xfs_attr_rmtval_set);
-DEFINE_ATTR_EVENT(xfs_attr_rmtval_remove);
#define DEFINE_DA_EVENT(name) \
DEFINE_EVENT(xfs_da_class, name, \