summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch12048
1 files changed, 12048 insertions, 0 deletions
diff --git a/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch b/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch
new file mode 100644
index 00000000..3e646328
--- /dev/null
+++ b/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch
@@ -0,0 +1,12048 @@
+From acc1741e372a209a2c0583b11eeee8409f07e264 Mon Sep 17 00:00:00 2001
+From: Christian Hopps <chopps@labn.net>
+Date: Sun, 16 Nov 2025 07:57:08 +0000
+Subject: [PATCH] Simplify and cleanup mgmtd
+
+PR https://github.com/FRRouting/frr/pull/20088
+
+Squashed all 18 commits for easier patch manipulation.
+
+Used commits:
+
+* d241d414ea mgmtd: fixes post reorganization
+* 47f5704bc2 mgmtd: reorganize and cleanup the code
+* 91f71c916d mgmtd: txn: remove copies of globals and simplify init/cleanup
+* ec273b53c8 mgmtd: move mgmt vty code out of share lib and into mgmtd
+* db9dc758c3 mgmtd: backend cleanup, remove locks and unused functions
+* a9d106ce9d mgmtd: remove unneeded lock/unlock (ref-count) on front-end adapters
+* 0c0a438ef4 mgmtd: front-end locking and candidate restore cleanup
+* 51351b8e23 mgmtd: rename MGMT_TXN_LOCK to TXN_INCREF
+* 5357a1befe mgmtd: standardize the clients sent and clients wait logic
+* fc7fc581e2 mgmtd: txn: refactor txn req structs
+* b7e995f72a mgmtd: simplify the config code/state machine
+* 9c0a02d598 lib: adapt mgmtd backend client code to simplification changes
+* 26190c2a4b lib: mgmtd: improve debug statement in front-end client code
+* bbc2792605 lib: mgmt_msg: add msg code to string function
+* 0dbc0c73bf lib: mgmt_msg: turn down the debug output
+* 8fde52f524 lib: change define so it can be passed as function "ptr".
+* 2345e038ab tools: recognize new FOREACH_BE_ADAPTER_BITS macro
+* 3e379b2a89 lib: add the `vty` arg to the start/end config callbacks
+---
+ .clang-format | 1 +
+ bgpd/bgp_vty.c | 4 +-
+ isisd/isis_main.c | 4 +-
+ lib/command.h | 4 +-
+ lib/lib_vty.c | 22 +-
+ lib/mgmt_be_client.c | 353 +++---
+ lib/mgmt_be_client.h | 8 +-
+ lib/mgmt_fe_client.c | 16 +-
+ lib/mgmt_fe_client.h | 8 +-
+ lib/mgmt_msg.c | 95 +-
+ lib/mgmt_msg_native.c | 64 +-
+ lib/mgmt_msg_native.h | 12 +-
+ lib/northbound_cli.c | 43 +-
+ lib/vty.c | 780 +-----------
+ lib/vty.h | 41 +-
+ mgmtd/mgmt.c | 6 +-
+ mgmtd/mgmt.h | 33 +-
+ mgmtd/mgmt_be_adapter.c | 973 +++++++--------
+ mgmtd/mgmt_be_adapter.h | 118 +-
+ mgmtd/mgmt_ds.c | 98 +-
+ mgmtd/mgmt_ds.h | 26 +-
+ mgmtd/mgmt_fe_adapter.c | 1848 ++++++++++++++--------------
+ mgmtd/mgmt_fe_adapter.h | 87 +-
+ mgmtd/mgmt_history.c | 16 +-
+ mgmtd/mgmt_main.c | 2 +
+ mgmtd/mgmt_memory.c | 3 -
+ mgmtd/mgmt_memory.h | 3 -
+ mgmtd/mgmt_txn.c | 2421 ++++++++-----------------------------
+ mgmtd/mgmt_txn.h | 282 ++---
+ mgmtd/mgmt_txn_cfg.c | 1096 +++++++++++++++++
+ mgmtd/mgmt_txn_priv.h | 80 ++
+ mgmtd/mgmt_vty.c | 66 +-
+ mgmtd/mgmt_vty_frontend.c | 898 ++++++++++++++
+ mgmtd/subdir.am | 3 +
+ ospf6d/ospf6_main.c | 4 +-
+ ospfd/ospf_main.c | 4 +-
+ sharpd/sharp_main.c | 4 +-
+ tools/checkpatch.pl | 2 +-
+ watchfrr/watchfrr.c | 4 +-
+ 39 files changed, 4604 insertions(+), 4928 deletions(-)
+ create mode 100644 mgmtd/mgmt_txn_cfg.c
+ create mode 100644 mgmtd/mgmt_txn_priv.h
+ create mode 100644 mgmtd/mgmt_vty_frontend.c
+
+diff --git a/.clang-format b/.clang-format
+index 89f554508a..f92767967a 100644
+--- a/.clang-format
++++ b/.clang-format
+@@ -105,6 +105,7 @@ ForEachMacros:
+ - 'FOREACH_AFI_SAFI'
+ - 'FOREACH_AFI_SAFI_NSF'
+ - 'FOREACH_BE_APPLY_BATCH_IN_LIST'
++ - 'FOREACH_BE_ADAPTER_BITS'
+ - 'FOREACH_BE_CLIENT_BITS'
+ - 'FOREACH_BE_TXN_BATCH_IN_LIST'
+ - 'FOREACH_BE_TXN_IN_LIST'
+diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
+index e21eeae317..a953790112 100644
+--- a/bgpd/bgp_vty.c
++++ b/bgpd/bgp_vty.c
+@@ -20668,7 +20668,7 @@ static void bgp_config_end_timeout(struct event *t)
+ bgp_config_finish(t);
+ }
+
+-static void bgp_config_start(void)
++static void bgp_config_start(struct vty *vty)
+ {
+ event_cancel(&t_bgp_cfg);
+ event_add_timer(bm->master, bgp_config_end_timeout, NULL,
+@@ -20680,7 +20680,7 @@ static void bgp_config_start(void)
+ * EoR before route-maps are processed.
+ * This is especially valid if using `bgp route-map delay-timer`.
+ */
+-static void bgp_config_end(void)
++static void bgp_config_end(struct vty *vty)
+ {
+ #define BGP_POST_CONFIG_DELAY_SECONDS 1
+ uint32_t bgp_post_config_delay =
+diff --git a/isisd/isis_main.c b/isisd/isis_main.c
+index a38e4ae54e..042cfd7466 100644
+--- a/isisd/isis_main.c
++++ b/isisd/isis_main.c
+@@ -214,14 +214,14 @@ static void isis_config_end_timeout(struct event *t)
+ isis_config_finish(t);
+ }
+
+-static void isis_config_start(void)
++static void isis_config_start(struct vty *vty)
+ {
+ event_cancel(&t_isis_cfg);
+ event_add_timer(im->master, isis_config_end_timeout, NULL,
+ ISIS_PRE_CONFIG_MAX_WAIT_SECONDS, &t_isis_cfg);
+ }
+
+-static void isis_config_end(void)
++static void isis_config_end(struct vty *vty)
+ {
+ /* If ISIS config processing thread isn't running, then
+ * we can return and rely it's properly handled.
+diff --git a/lib/command.h b/lib/command.h
+index ea08a74d6a..d5119fd97c 100644
+--- a/lib/command.h
++++ b/lib/command.h
+@@ -611,8 +611,8 @@ extern int cmd_execute_command(vector, struct vty *,
+ extern int cmd_execute_command_strict(vector, struct vty *,
+ const struct cmd_element **);
+ extern void cmd_init(int terminal);
+-extern void cmd_init_config_callbacks(void (*start_config_cb)(void),
+- void (*end_config_cb)(void));
++extern void cmd_init_config_callbacks(void (*start_config_cb)(struct vty *vty),
++ void (*end_config_cb)(struct vty *vty));
+ extern void cmd_terminate(void);
+ extern void cmd_exit(struct vty *vty);
+ extern int cmd_list_cmds(struct vty *vty, int do_permute);
+diff --git a/lib/lib_vty.c b/lib/lib_vty.c
+index 66dda7d0b4..1b476bca8b 100644
+--- a/lib/lib_vty.c
++++ b/lib/lib_vty.c
+@@ -207,8 +207,8 @@ DEFUN (frr_version,
+ static struct call_back {
+ time_t readin_time;
+
+- void (*start_config)(void);
+- void (*end_config)(void);
++ void (*start_config)(struct vty *vty);
++ void (*end_config)(struct vty *vty);
+ } callback;
+
+
+@@ -220,7 +220,7 @@ DEFUN_NOSH(start_config, start_config_cmd, "XFRR_start_configuration",
+ vty->pending_allowed = 1;
+
+ if (callback.start_config)
+- (*callback.start_config)();
++ (*callback.start_config)(vty);
+
+ return CMD_SUCCESS;
+ }
+@@ -238,6 +238,7 @@ DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration",
+ frrtime_to_interval(readin_time, readin_time_str,
+ sizeof(readin_time_str));
+
++ /* This is also getting cleared in the config node exit */
+ vty->pending_allowed = 0;
+ ret = nb_cli_pending_commit_check(vty);
+
+@@ -245,23 +246,14 @@ DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration",
+ zlog_debug("%s: VTY:%p, pending SET-CFG: %u", __func__, vty,
+ (uint32_t)vty->mgmt_num_pending_setcfg);
+
+- /*
+- * If (and only if) we have sent any CLI config commands to MGMTd
+- * FE interface using vty_mgmt_send_config_data() without implicit
+- * commit before, should we need to send an explicit COMMIT-REQ now
+- * to apply all those commands at once.
+- */
+- if (vty->mgmt_num_pending_setcfg && vty_mgmt_fe_enabled())
+- vty_mgmt_send_commit_config(vty, false, false, false);
+-
+ if (callback.end_config)
+- (*callback.end_config)();
++ (*callback.end_config)(vty);
+
+ return ret;
+ }
+
+-void cmd_init_config_callbacks(void (*start_config_cb)(void),
+- void (*end_config_cb)(void))
++void cmd_init_config_callbacks(void (*start_config_cb)(struct vty *vty),
++ void (*end_config_cb)(struct vty *vty))
+ {
+ callback.start_config = start_config_cb;
+ callback.end_config = end_config_cb;
+diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c
+index 6a5ca6649f..6a611d7611 100644
+--- a/lib/mgmt_be_client.c
++++ b/lib/mgmt_be_client.c
+@@ -33,7 +33,6 @@ struct be_oper_iter_arg {
+ struct lyd_node *hint; /* last node added */
+ };
+
+-PREDECL_LIST(mgmt_be_txns);
+ struct mgmt_be_txn_ctx {
+ /* Txn-Id as assigned by MGMTD */
+ uint64_t txn_id;
+@@ -42,21 +41,17 @@ struct mgmt_be_txn_ctx {
+ struct mgmt_be_client_txn_ctx client_data;
+ struct mgmt_be_client *client;
+
+- struct mgmt_be_txns_item list_linkage;
+-
++ struct nb_config *candidate_config;
+ struct nb_transaction *nb_txn;
+ uint32_t nb_txn_id;
+ };
+ #define MGMTD_BE_TXN_FLAGS_CFGPREP_FAILED (1U << 1)
+
+-DECLARE_LIST(mgmt_be_txns, struct mgmt_be_txn_ctx, list_linkage);
+-
+ struct mgmt_be_client {
+ struct msg_client client;
+
+ char *name;
+
+- struct nb_config *candidate_config;
+ struct nb_config *running_config;
+
+ uint64_t num_edit_nb_cfg;
+@@ -66,15 +61,12 @@ struct mgmt_be_client {
+ uint64_t num_apply_nb_cfg;
+ uint64_t avg_apply_nb_cfg_tm;
+
+- struct mgmt_be_txns_head txn_head;
++ struct mgmt_be_txn_ctx *config_txn;
+
+ struct mgmt_be_client_cbs cbs;
+ uintptr_t user_data;
+ };
+
+-#define FOREACH_BE_TXN_IN_LIST(client_ctx, txn) \
+- frr_each_safe (mgmt_be_txns, &(client_ctx)->txn_head, (txn))
+-
+ struct debug mgmt_dbg_be_client = {
+ .conf = "debug mgmt client backend",
+ .desc = "Management backend client operations",
+@@ -96,15 +88,10 @@ static struct mgmt_be_txn_ctx *
+ mgmt_be_find_txn_by_id(struct mgmt_be_client *client_ctx, uint64_t txn_id,
+ bool warn)
+ {
+- struct mgmt_be_txn_ctx *txn = NULL;
+-
+- FOREACH_BE_TXN_IN_LIST (client_ctx, txn)
+- if (txn->txn_id == txn_id)
+- return txn;
++ if (client_ctx->config_txn && client_ctx->config_txn->txn_id == txn_id)
++ return client_ctx->config_txn;
+ if (warn)
+- log_err_be_client("client %s unkonwn txn-id: %" PRIu64,
+- client_ctx->name, txn_id);
+-
++ log_err_be_client("client %s unkonwn txn-id: %Lu", client_ctx->name, txn_id);
+ return NULL;
+ }
+
+@@ -123,53 +110,54 @@ mgmt_be_txn_create(struct mgmt_be_client *client_ctx, uint64_t txn_id)
+ txn = XCALLOC(MTYPE_MGMTD_BE_TXN, sizeof(struct mgmt_be_txn_ctx));
+ txn->txn_id = txn_id;
+ txn->client = client_ctx;
+- mgmt_be_txns_add_tail(&client_ctx->txn_head, txn);
++ /*
++ * NOTE: If this is ends up being expensive then we should just offload
++ * creating one in a thread whenever updating running_config.
++ */
++ txn->candidate_config = nb_config_dup(client_ctx->running_config);
++ if (!txn->candidate_config) {
++ log_err_be_client("Failed to create candidate config for txn-id: %Lu", txn_id);
++ XFREE(MTYPE_MGMTD_BE_TXN, txn);
++ return NULL;
++ }
++ /* record that we have one going */
++ client_ctx->config_txn = txn;
+
+ debug_be_client("Created new txn-id: %" PRIu64, txn_id);
+
+ return txn;
+ }
+
+-static void mgmt_be_txn_delete(struct mgmt_be_client *client_ctx,
+- struct mgmt_be_txn_ctx **txn)
++static void mgmt_be_txn_delete(struct mgmt_be_txn_ctx *txn)
+ {
++ struct mgmt_be_client *client_ctx;
+ char err_msg[] = "MGMT Transaction Delete";
+
+ if (!txn)
+ return;
+-
+ /*
+- * Remove the transaction from the list of transactions
+- * so that future lookups with the same transaction id
+- * does not return this one.
++ * Stop tracking this transaction from the client context
+ */
+- mgmt_be_txns_del(&client_ctx->txn_head, *txn);
++ client_ctx = txn->client;
++ assert(client_ctx->config_txn == txn);
++ client_ctx->config_txn = NULL;
+
+ /*
+- * Time to delete the transaction which should also take care of
+- * cleaning up all batches created via CFG_REQs. But first notify the
+- * client about the transaction delete.
++ * Delete the northbound transaction which should also take care
++ * of cleaning up any allocations made so far.
+ */
+- if (client_ctx->cbs.txn_notify)
+- (void)(*client_ctx->cbs.txn_notify)(client_ctx,
+- client_ctx->user_data,
+- &(*txn)->client_data, true);
++ if (txn->nb_txn)
++ nb_candidate_commit_abort(txn->nb_txn, err_msg, sizeof(err_msg));
+
+- if ((*txn)->nb_txn)
+- nb_candidate_commit_abort((*txn)->nb_txn, err_msg,
+- sizeof(err_msg));
+- XFREE(MTYPE_MGMTD_BE_TXN, *txn);
++ nb_config_free(txn->candidate_config);
+
+- *txn = NULL;
++ XFREE(MTYPE_MGMTD_BE_TXN, txn);
+ }
+
+ static void mgmt_be_cleanup_all_txns(struct mgmt_be_client *client_ctx)
+ {
+- struct mgmt_be_txn_ctx *txn = NULL;
+-
+- FOREACH_BE_TXN_IN_LIST (client_ctx, txn) {
+- mgmt_be_txn_delete(client_ctx, &txn);
+- }
++ mgmt_be_txn_delete(client_ctx->config_txn);
++ assert(client_ctx->config_txn == NULL);
+ }
+
+
+@@ -308,65 +296,10 @@ static int mgmt_be_send_notification(void *__client, const char *path, const str
+ return 0;
+ }
+
+-static int mgmt_be_send_txn_reply(struct mgmt_be_client *client, uint64_t txn_id, bool create)
+-{
+- struct mgmt_msg_txn_reply *msg;
+- int ret;
+-
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_reply, 0, MTYPE_MSG_NATIVE_TXN_REPLY);
+- msg->code = MGMT_MSG_CODE_TXN_REPLY;
+- msg->refer_id = txn_id;
+- msg->created = create;
+-
+- debug_be_client("Sending TXN_REPLY txn-id %Lu", txn_id);
+-
+- ret = be_client_send_native_msg(client, msg, mgmt_msg_native_get_msg_len(msg), false);
+- mgmt_msg_native_free_msg(msg);
+- return ret;
+-}
+-
+ /* ===================== */
+ /* CONFIGURATION PROCESS */
+ /* ===================== */
+
+-/* ------------------ */
+-/* Create Transaction */
+-/* ------------------ */
+-
+-/*
+- * Process transaction create message
+- */
+-static void be_client_handle_txn_req(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf,
+- size_t msg_len)
+-{
+- struct mgmt_msg_txn_req *msg = msgbuf;
+- struct mgmt_be_txn_ctx *txn;
+-
+- debug_be_client("Got TXN_REQ for client %s txn-id %Lu req-id %Lu", client->name, txn_id,
+- msg->req_id);
+-
+- if (msg->create) {
+- debug_be_client("Creating new txn-id %" PRIu64, txn_id);
+-
+- txn = mgmt_be_txn_create(client, txn_id);
+- if (!txn) {
+- msg_conn_disconnect(&client->client.conn, true);
+- return;
+- }
+-
+- if (client->cbs.txn_notify)
+- (*client->cbs.txn_notify)(client, client->user_data, &txn->client_data,
+- false);
+- } else {
+- debug_be_client("Deleting txn-id: %" PRIu64, txn_id);
+- txn = mgmt_be_find_txn_by_id(client, txn_id, false);
+- if (txn)
+- mgmt_be_txn_delete(client, &txn);
+- }
+-
+- mgmt_be_send_txn_reply(client, txn_id, msg->create);
+-}
+-
+ /* ------------------------- */
+ /* Receive Config from MgmtD */
+ /* ------------------------- */
+@@ -405,17 +338,6 @@ static void mgmt_be_txn_cfg_abort(struct mgmt_be_txn_ctx *txn)
+ nb_candidate_commit_abort(txn->nb_txn, errmsg, sizeof(errmsg));
+ txn->nb_txn = 0;
+ }
+-
+- /*
+- * revert candidate back to running
+- *
+- * This is one txn ctx but the candidate_config is per client ctx, how
+- * does that work?
+- */
+- debug_be_client("Reset candidate configurations after abort of txn-id: %" PRIu64,
+- txn->txn_id);
+- nb_config_replace(txn->client->candidate_config,
+- txn->client->running_config, true);
+ }
+
+ /* ------------------------------------------------ */
+@@ -470,10 +392,29 @@ static bool be_client_change_edit_config(struct nb_config *candidate_config, con
+ return error;
+ }
+
+-static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **config)
++/**
++ * be_txn_config_prepare() - Prepare to apply the given configuration.
++ * @client_ctx: The backend client context.
++ * @txn_id: The transaction id for this configuration change.
++ *
++ * The configuration changes are applied to the candidate configuration and
++ * validated. If the configuration is valid it is saved in a northbound
++ * transaction that is saved in a mgmtd txn associated with the @txn_id.
++ *
++ * If there is a config validation error the error is returned to mgmtd. For any
++ * other error the connection is dropped.
++ *
++ * Currently the global candidate configuration is used. This will be changed to
++ * a per TXN candidate eventually. For now it is assumed that we only have one
++ * change TXN at a time.
++ *
++ * Return: true if the connection should be dropped.
++ */
++static bool mgmt_be_txn_cfg_prepare(struct mgmt_be_client *client_ctx, uint64_t txn_id,
++ const char **config)
+ {
+- struct mgmt_be_client *client_ctx;
+- struct nb_context nb_ctx = {0};
++ struct mgmt_be_txn_ctx *txn;
++ struct nb_context nb_ctx = { 0 };
+ struct timeval edit_nb_cfg_start;
+ struct timeval edit_nb_cfg_end;
+ unsigned long edit_nb_cfg_tm;
+@@ -483,58 +424,76 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **con
+ bool error;
+ char err_buf[BUFSIZ];
+ size_t num_processed;
++ bool disconnect = false;
+ int err;
+
+- assert(txn && txn->client);
+- client_ctx = txn->client;
+-
+ num_processed = 0;
+- error = false;
+ nb_ctx.client = NB_CLIENT_CLI;
+ nb_ctx.user = (void *)client_ctx->user_data;
+ err_buf[0] = 0;
+
++ debug_be_client("Creating new txn-id %Lu", txn_id);
++
+ /*
+- * this is always true, right?!
++ * First validate there's no other config txn right now. Eventually we
++ * may want to support multiple CFG_REQ prioer to a CFG_APPLY.
+ */
+- assert(!txn->nb_txn);
++ if (client_ctx->config_txn) {
++ log_err_be_client("Cannot prepare cfg for txn-id: %Lu, another txn-id: %Lu was in progress",
++ txn_id, client_ctx->config_txn->txn_id);
++ return true;
++ }
+
+- if (!txn->nb_txn) {
+- /*
+- * XXX, is this just an old crusty comment?
+- *
+- * This happens when the current backend client is only
+- * interested in consuming the config items but is not
+- * interested in validating it.
+- */
+- gettimeofday(&edit_nb_cfg_start, NULL);
+-
+- error = be_client_change_edit_config(client_ctx->candidate_config, config, err_buf,
+- sizeof(err_buf));
+- if (error) {
+- log_err_be_client("Failed to update configs for txn-id: %" PRIu64
+- " to candidate, err: '%s'",
+- txn->txn_id, err_buf);
+- return -1;
+- }
++ /* Create a txn to track the config changes for later apply */
++ txn = mgmt_be_txn_create(client_ctx, txn_id);
++ if (!txn) {
++ log_err_be_client("Failed to create txn for txn_id: %Lu", txn_id);
++ return true;
++ }
+
+- gettimeofday(&edit_nb_cfg_end, NULL);
+- edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, edit_nb_cfg_start);
+- client_ctx->avg_edit_nb_cfg_tm =
+- ((client_ctx->avg_edit_nb_cfg_tm * client_ctx->num_edit_nb_cfg) +
+- edit_nb_cfg_tm) /
+- (client_ctx->num_edit_nb_cfg + 1);
+- client_ctx->num_edit_nb_cfg++;
++ /* Apply the list of config changes to the candidate config */
++ gettimeofday(&edit_nb_cfg_start, NULL);
++ error = be_client_change_edit_config(txn->candidate_config, config, err_buf,
++ sizeof(err_buf));
++ if (error) {
++ log_err_be_client("Failed to update configs for txn-id: %" PRIu64
++ " to candidate, err: '%s'",
++ txn->txn_id, err_buf);
++ disconnect = true;
++ goto done;
+ }
+
++ gettimeofday(&edit_nb_cfg_end, NULL);
++ edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, edit_nb_cfg_start);
++ client_ctx->avg_edit_nb_cfg_tm =
++ ((client_ctx->avg_edit_nb_cfg_tm * client_ctx->num_edit_nb_cfg) + edit_nb_cfg_tm) /
++ (client_ctx->num_edit_nb_cfg + 1);
++ client_ctx->num_edit_nb_cfg++;
++
++ /* Create a northbound transaction and prepare for the changes */
++
++ /*
++ * XXX this takes the candidate config we just applied a list of config
++ * changes to, creates a diff, then creates a new list of changes,
++ * again, so that we can use that to call the callbacks. Would should
++ * just use the original list!
++ *
++ * We do need the changes applied to the candidate config for validation
++ * and to eventually replace the running config, but we should be able
++ * to go back and just use the original list. The only possible issue is
++ * if the list includes any new changes. We should maybe build a new
++ * list and then validate that it is the same as the original lsit (or a
++ * subset of it) in development mode, but not in production build.
++ *
++ * Anyway we should time it to see if this is a big deal or not.
++ */
+ gettimeofday(&prep_nb_cfg_start, NULL);
+- err = nb_candidate_commit_prepare(nb_ctx, client_ctx->candidate_config,
+- "MGMTD Backend Txn", &txn->nb_txn,
+- false, true,
+- err_buf, sizeof(err_buf) - 1);
++ err = nb_candidate_commit_prepare(nb_ctx, txn->candidate_config, "MGMTD Backend Txn",
++ &txn->nb_txn, false, true, err_buf, sizeof(err_buf) - 1);
+ if (err != NB_OK) {
+ err_buf[sizeof(err_buf) - 1] = 0;
+ if (err == NB_ERR_VALIDATION) {
++ /* maybe symantic validation error isn't a LOGERR? */
+ log_err_be_client("Failed to validate configs txn-id: %" PRIu64
+ " %zu batches, err: '%s'",
+ txn->txn_id, num_processed, err_buf);
+@@ -556,38 +515,39 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **con
+
+ gettimeofday(&prep_nb_cfg_end, NULL);
+ prep_nb_cfg_tm = timeval_elapsed(prep_nb_cfg_end, prep_nb_cfg_start);
+- client_ctx->avg_prep_nb_cfg_tm = ((client_ctx->avg_prep_nb_cfg_tm *
+- client_ctx->num_prep_nb_cfg) +
+- prep_nb_cfg_tm) /
+- (client_ctx->num_prep_nb_cfg + 1);
++ client_ctx->avg_prep_nb_cfg_tm =
++ ((client_ctx->avg_prep_nb_cfg_tm * client_ctx->num_prep_nb_cfg) + prep_nb_cfg_tm) /
++ (client_ctx->num_prep_nb_cfg + 1);
+ client_ctx->num_prep_nb_cfg++;
+
+- mgmt_be_send_cfg_reply(client_ctx, txn->txn_id, !error, error ? err_buf : NULL);
++ /*
++ * Replying to mgmtd
++ */
++ disconnect = !!mgmt_be_send_cfg_reply(client_ctx, txn->txn_id, !error,
++ error ? err_buf : NULL);
+
+ debug_be_client("Avg-nb-edit-duration %Lu uSec, nb-prep-duration %lu (avg: %Lu) uSec, batch size %u",
+ client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm,
+ client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed);
+-
+- if (error)
++done:
++ /*
++ * If we have any error we abort the new transaction and delete the txn
++ */
++ if (error || disconnect) {
+ mgmt_be_txn_cfg_abort(txn);
++ mgmt_be_txn_delete(txn);
++ }
+
+- return 0;
++ return disconnect;
+ }
+
+ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf,
+ size_t msg_len)
+ {
+ struct mgmt_msg_cfg_req *msg = msgbuf;
+- struct mgmt_be_txn_ctx *txn;
+ const char **config = NULL;
+
+- debug_be_client("Got CFG_DATA_REQ txn-id: %Lu", txn_id);
+-
+- txn = mgmt_be_find_txn_by_id(client, txn_id, true);
+- if (!txn) {
+- log_err_be_client("Can't find TXN for txn_id: %Lu", txn_id);
+- goto failed;
+- }
++ debug_be_client("Got CFG_REQ txn-id: %Lu", txn_id);
+
+ config = mgmt_msg_native_strings_decode(msg, msg_len, msg->config);
+ if (darr_len(config) == 0) {
+@@ -595,9 +555,9 @@ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id,
+ goto failed;
+ }
+
+- debug_be_client("End of data; CFG_PREPARE_REQ processing");
++ debug_be_client("CFG_REQ processing");
+
+- if (mgmt_be_txn_cfg_prepare(txn, config))
++ if (mgmt_be_txn_cfg_prepare(client, txn_id, config))
+ goto failed;
+
+ darr_free_free(config);
+@@ -605,6 +565,7 @@ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id,
+
+ failed:
+ darr_free_free(config);
++ log_err_be_client("Disconnecting client %s due to error handling CFG_REQ", client->name);
+ msg_conn_disconnect(&client->client.conn, true);
+ }
+
+@@ -631,16 +592,18 @@ static int mgmt_be_send_apply_reply(struct mgmt_be_client *client_ctx,
+ return ret;
+ }
+
+-static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
++static bool mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
+ {
+ struct mgmt_be_client *client_ctx;
+ struct timeval apply_nb_cfg_start;
+ struct timeval apply_nb_cfg_end;
+ unsigned long apply_nb_cfg_tm;
+ char err_buf[BUFSIZ];
++ bool disconnect;
+
+ assert(txn && txn->client);
+ client_ctx = txn->client;
++ assert(client_ctx->config_txn == txn);
+
+ assert(txn->nb_txn);
+
+@@ -648,8 +611,7 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
+ * Now apply all the batches we have applied in one go.
+ */
+ gettimeofday(&apply_nb_cfg_start, NULL);
+- (void)nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id,
+- err_buf, sizeof(err_buf) - 1);
++ nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id, err_buf, sizeof(err_buf) - 1);
+ gettimeofday(&apply_nb_cfg_end, NULL);
+
+ apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start);
+@@ -659,25 +621,54 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
+ client_ctx->num_apply_nb_cfg++;
+ txn->nb_txn = NULL;
+
+- mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL);
++ disconnect = !!mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL);
+
+ debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm,
+ client_ctx->avg_apply_nb_cfg_tm);
+
+- return 0;
++ mgmt_be_txn_delete(txn);
++
++ return disconnect;
+ }
+
+ static void be_client_handle_cfg_apply(struct mgmt_be_client *client, uint64_t txn_id)
+ {
+- struct mgmt_be_txn_ctx *txn;
++ struct mgmt_be_txn_ctx *txn = NULL;
+
+ debug_be_client("Got CFG_APPLY_REQ for client %s txn-id %Lu", client->name, txn_id);
+
+- txn = mgmt_be_find_txn_by_id(client, txn_id, true);
++ if (client->config_txn && client->config_txn->txn_id == txn_id)
++ txn = client->config_txn;
++ else
++ log_err_be_client("client %s unkonwn config txn-id: %Lu", client->name, txn_id);
++
+ if (!txn || mgmt_be_txn_proc_cfgapply(txn))
+ msg_conn_disconnect(&client->client.conn, true);
+ }
+
++/*
++ * This currently serves as the CFG_ABORT
++ */
++static void be_client_handle_txn_req(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf,
++ size_t msg_len)
++{
++ struct mgmt_msg_txn_req *msg = msgbuf;
++
++ debug_be_client("Got TXN_DELETE txn-id: %Lu", txn_id);
++ if (msg->create) {
++ log_err_be_client("Unsupported TXN_REQ create for txn-id: %Lu", txn_id);
++ goto failed;
++ }
++ if (!client->config_txn || client->config_txn->txn_id != txn_id) {
++ log_err_be_client("Cannot find TXN for TXN_DELETE txn-id: %Lu", txn_id);
++ goto failed;
++ }
++ mgmt_be_txn_delete(client->config_txn);
++ return;
++failed:
++ msg_conn_disconnect(&client->client.conn, true);
++}
++
+
+ struct be_client_tree_data_batch_args {
+ struct mgmt_be_client *client;
+@@ -1009,6 +1000,7 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client,
+
+ switch (msg->code) {
+ case MGMT_MSG_CODE_TXN_REQ:
++ /* XXX used as a CFG_ABORT_REQ */
+ be_client_handle_txn_req(client, txn_id, msg, msg_len);
+ break;
+ case MGMT_MSG_CODE_CFG_REQ:
+@@ -1119,8 +1111,12 @@ static int _notify_conenct_disconnect(struct msg_client *msg_client,
+ if (connected) {
+ assert(msg_client->conn.fd != -1);
+ ret = mgmt_be_send_subscribe(client);
+- if (ret)
++ if (ret) {
++ log_err_be_client("Failed to send subscribe on connect: %s", client->name);
+ return ret;
++ }
++ debug_be_client("Sent subscribe on connect: %s: fd: %d", client->name,
++ msg_client->conn.fd);
+ }
+
+ /* Notify BE client through registered callback (if any) */
+@@ -1227,7 +1223,12 @@ static enum nb_error clients_client_state_candidate_config_version_get(
+ const struct nb_node *nb_node, const void *parent_list_entry, struct lyd_node *parent)
+ {
+ const struct lysc_node *snode = nb_node->snode;
+- uint64_t value = __be_client->candidate_config->version;
++ uint64_t value;
++
++ if (__be_client->config_txn)
++ value = __be_client->config_txn->candidate_config->version;
++ else
++ value = __be_client->running_config->version;
+
+ if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
+ LYD_NEW_PATH_UPDATE, NULL))
+@@ -1376,10 +1377,9 @@ struct mgmt_be_client *mgmt_be_client_create(const char *client_name,
+
+ client->name = XSTRDUP(MTYPE_MGMTD_BE_CLIENT_NAME, client_name);
+ client->running_config = running_config;
+- client->candidate_config = vty_shared_candidate_config;
++ // client->candidate_config = vty_shared_candidate_config;
+ if (cbs)
+ client->cbs = *cbs;
+- mgmt_be_txns_init(&client->txn_head);
+
+ snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME);
+
+@@ -1417,7 +1417,6 @@ void mgmt_be_client_destroy(struct mgmt_be_client *client)
+ nb_oper_cancel_all_walks();
+ msg_client_cleanup(&client->client);
+ mgmt_be_cleanup_all_txns(client);
+- mgmt_be_txns_fini(&client->txn_head);
+
+ XFREE(MTYPE_MGMTD_BE_CLIENT_NAME, client->name);
+ XFREE(MTYPE_MGMTD_BE_CLIENT, client);
+diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h
+index d0b1f12ceb..5d1aeb7b97 100644
+--- a/lib/mgmt_be_client.h
++++ b/lib/mgmt_be_client.h
+@@ -54,12 +54,8 @@ struct mgmt_be_client_txn_ctx {
+ *
+ */
+ struct mgmt_be_client_cbs {
+- void (*client_connect_notify)(struct mgmt_be_client *client,
+- uintptr_t usr_data, bool connected);
+- void (*txn_notify)(struct mgmt_be_client *client, uintptr_t usr_data,
+- struct mgmt_be_client_txn_ctx *txn_ctx,
+- bool destroyed);
+-
++ void (*client_connect_notify)(struct mgmt_be_client *client, uintptr_t usr_data,
++ bool connected);
+ const char **config_xpaths;
+ uint nconfig_xpaths;
+ const char **oper_xpaths;
+diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c
+index 4fed87f0d3..95d0bafa6b 100644
+--- a/lib/mgmt_fe_client.c
++++ b/lib/mgmt_fe_client.c
+@@ -266,9 +266,9 @@ int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t session_id,
+ return ret;
+ }
+
+-int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id,
+- enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dest_ds_id,
+- bool validate_only, bool abort, bool unlock)
++int mgmt_fe_send_commit_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id,
++ enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dest_ds_id,
++ bool validate_only, bool abort, bool unlock)
+ {
+ struct mgmt_msg_commit *msg;
+ int ret;
+@@ -287,8 +287,12 @@ int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_i
+ msg->action = MGMT_MSG_COMMIT_APPLY;
+ msg->unlock = unlock;
+
+- debug_fe_client("Sending COMMIT message for Src-DS:%s, Dst-DS:%s session-id %Lu",
+- dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id);
++ debug_fe_client("Sending COMMIT message for src: %s dst: %s session-id %Lu action: %s unlock: %d",
++ dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id,
++ msg->action == MGMT_MSG_COMMIT_VALIDATE ? "validate"
++ : msg->action == MGMT_MSG_COMMIT_ABORT ? "abort"
++ : "apply",
++ unlock);
+
+ ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false);
+ ret = fe_client_push_sent_msg(client, (struct mgmt_msg_header *)msg, ret);
+@@ -695,7 +699,7 @@ static void mgmt_fe_client_process_msg(uint8_t version, uint8_t *data,
+ return;
+ }
+
+- log_err_fe_client("Protobuf no longer used in backend API");
++ log_err_fe_client("Protobuf no longer used in frontend API");
+ msg_conn_disconnect(&client->client.conn, true);
+ }
+
+diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h
+index 22b167453a..8ff08b566a 100644
+--- a/lib/mgmt_fe_client.h
++++ b/lib/mgmt_fe_client.h
+@@ -232,10 +232,10 @@ extern int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t sessi
+ * Returns:
+ * 0 on success, otherwise msg_conn_send_msg() return values.
+ */
+-extern int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_id,
+- uint64_t req_id, enum mgmt_ds_id src_ds_id,
+- enum mgmt_ds_id dst_ds_id, bool validate_only, bool abort,
+- bool unlock);
++extern int mgmt_fe_send_commit_req(struct mgmt_fe_client *client, uint64_t session_id,
++ uint64_t req_id, enum mgmt_ds_id src_ds_id,
++ enum mgmt_ds_id dst_ds_id, bool validate_only, bool abort,
++ bool unlock);
+
+ /*
+ * Send GET-DATA to MGMTD daemon.
+diff --git a/lib/mgmt_msg.c b/lib/mgmt_msg.c
+index 8f22b31231..615e353217 100644
+--- a/lib/mgmt_msg.c
++++ b/lib/mgmt_msg.c
+@@ -17,6 +17,14 @@
+ #include "mgmt_msg.h"
+ #include "mgmt_msg_native.h"
+
++static bool trace;
++
++#define MGMT_MSG_TRACE(dbgtag, fmt, ...) \
++ do { \
++ if (dbgtag && trace) \
++ zlog_debug("%s: %s: " fmt, dbgtag, __func__, ##__VA_ARGS__); \
++ } while (0)
++
+
+ #define MGMT_MSG_DBG(dbgtag, fmt, ...) \
+ do { \
+@@ -56,6 +64,7 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd,
+ ssize_t n, left;
+
+ assert(ms && fd != -1);
++ MGMT_MSG_TRACE(dbgtag, "enter with %zu bytes available to read on fd %d", avail, fd);
+
+ /*
+ * Read as much as we can into the stream.
+@@ -65,19 +74,18 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd,
+
+ /* -2 is normal nothing read, and to retry */
+ if (n == -2) {
+- MGMT_MSG_DBG(dbgtag, "nothing more to read");
++ MGMT_MSG_TRACE(dbgtag, "nothing more to read on fd %d", fd);
+ break;
+ }
+ if (n <= 0) {
+ if (n == 0)
+- MGMT_MSG_ERR(ms, "got EOF/disconnect");
++ MGMT_MSG_ERR(ms, "got EOF/disconnect on fd %d", fd);
+ else
+- MGMT_MSG_ERR(ms,
+- "got error while reading: '%s'",
++ MGMT_MSG_ERR(ms, "got error while reading on fd %d: '%s'", fd,
+ safe_strerror(errno));
+ return MSR_DISCONNECT;
+ }
+- MGMT_MSG_DBG(dbgtag, "read %zd bytes", n);
++ MGMT_MSG_TRACE(dbgtag, "read %zd bytes on fd %d", n, fd);
+ ms->nrxb += n;
+ avail -= n;
+ }
+@@ -90,13 +98,13 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd,
+ while (left > (ssize_t)sizeof(struct mgmt_msg_hdr)) {
+ mhdr = (struct mgmt_msg_hdr *)(STREAM_DATA(ms->ins) + total);
+ if (!MGMT_MSG_IS_MARKER(mhdr->marker)) {
+- MGMT_MSG_DBG(dbgtag, "recv corrupt buffer, disconnect");
++ MGMT_MSG_DBG(dbgtag, "recv corrupt buffer on fd %d, disconnect", fd);
+ return MSR_DISCONNECT;
+ }
+ if ((ssize_t)mhdr->len > left)
+ break;
+
+- MGMT_MSG_DBG(dbgtag, "read full message len %u", mhdr->len);
++ MGMT_MSG_TRACE(dbgtag, "read full message on fd %d len %u", fd, mhdr->len);
+ total += mhdr->len;
+ left -= mhdr->len;
+ mcount++;
+@@ -166,7 +174,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms,
+ uint8_t *data;
+ size_t left, nproc;
+
+- MGMT_MSG_DBG(dbgtag, "Have %zu streams to process", ms->inq.count);
++ MGMT_MSG_TRACE(dbgtag, "Have %zu streams to process", ms->inq.count);
+
+ nproc = 0;
+ while (nproc < ms->max_read_buf) {
+@@ -176,7 +184,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms,
+
+ data = STREAM_DATA(work);
+ left = stream_get_endp(work);
+- MGMT_MSG_DBG(dbgtag, "Processing stream of len %zu", left);
++ MGMT_MSG_TRACE(dbgtag, "Processing stream of len %zu", left);
+
+ for (; left > sizeof(struct mgmt_msg_hdr);
+ left -= mhdr->len, data += mhdr->len) {
+@@ -185,10 +193,11 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms,
+ assert(MGMT_MSG_IS_MARKER(mhdr->marker));
+ assert(left >= mhdr->len);
+
+- handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker),
+- (uint8_t *)(mhdr + 1),
+- mhdr->len - sizeof(struct mgmt_msg_hdr),
+- user);
++ /*
++ * Q: if the handler disconnects should stop/flush?
++ */
++ handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), (uint8_t *)(mhdr + 1),
++ mhdr->len - sizeof(struct mgmt_msg_hdr), user);
+ ms->nrxm++;
+ nproc++;
+ }
+@@ -230,9 +239,8 @@ enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd,
+ ssize_t n;
+
+ if (ms->outs) {
+- MGMT_MSG_DBG(dbgtag,
+- "found unqueued stream with %zu bytes, queueing",
+- stream_get_endp(ms->outs));
++ MGMT_MSG_TRACE(dbgtag, "found unqueued stream with %zu bytes on fd %d, queueing",
++ stream_get_endp(ms->outs), fd);
+ stream_fifo_push(&ms->outq, ms->outs);
+ ms->outs = NULL;
+ }
+@@ -245,46 +253,40 @@ enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd,
+ n = stream_flush(s, fd);
+ if (n <= 0) {
+ if (n == 0)
+- MGMT_MSG_ERR(ms,
+- "connection closed while writing");
++ MGMT_MSG_ERR(ms, "connection closed while writing on fd %d", fd);
+ else if (ERRNO_IO_RETRY(errno)) {
+- MGMT_MSG_DBG(
+- dbgtag,
+- "retry error while writing %zd bytes: %s (%d)",
+- left, safe_strerror(errno), errno);
++ MGMT_MSG_DBG(dbgtag,
++ "retry error while writing %zd bytes on fd %d: %s (%d)",
++ left, fd, safe_strerror(errno), errno);
+ return MSW_SCHED_STREAM;
+ } else
+- MGMT_MSG_ERR(
+- ms,
+- "error while writing %zd bytes: %s (%d)",
+- left, safe_strerror(errno), errno);
++ MGMT_MSG_ERR(ms, "error while writing %zd bytes on fd %d: %s (%d)",
++ left, fd, safe_strerror(errno), errno);
+
+ n = mgmt_msg_reset_writes(ms);
+- MGMT_MSG_DBG(dbgtag, "drop and freed %zd streams", n);
++ MGMT_MSG_ERR(ms, "drop, freed %zd streams on fd %d for disconnect", n, fd);
+
+ return MSW_DISCONNECT;
+ }
+
+ ms->ntxb += n;
+ if (n != left) {
+- MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd", n,
+- left);
++ MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd on fd %d", n, left, fd);
+ stream_forward_getp(s, n);
+ return MSW_SCHED_STREAM;
+ }
+
+ stream_free(stream_fifo_pop(&ms->outq));
+- MGMT_MSG_DBG(dbgtag, "wrote stream of %zd bytes", n);
++ MGMT_MSG_TRACE(dbgtag, "wrote stream of %zd bytes on fd %d", n, fd);
+ nproc++;
+ }
+ if (s) {
+- MGMT_MSG_DBG(
+- dbgtag,
+- "reached %zu buffer writes, pausing with %zu streams left",
+- ms->max_write_buf, ms->outq.count);
++ MGMT_MSG_DBG(dbgtag,
++ "reached %zu buffer writes on fd %d, pausing with %zu streams left",
++ ms->max_write_buf, fd, ms->outq.count);
+ return MSW_SCHED_STREAM;
+ }
+- MGMT_MSG_DBG(dbgtag, "flushed all streams from output q");
++ MGMT_MSG_TRACE(dbgtag, "flushed all streams from output q for fd %d", fd);
+ return MSW_SCHED_NONE;
+ }
+
+@@ -317,30 +319,27 @@ int mgmt_msg_send_msg(struct mgmt_msg_state *ms, uint8_t version, void *msg,
+ size_t mlen = len + sizeof(*mhdr);
+
+ if (mlen > ms->max_msg_sz)
+- MGMT_MSG_DBG(dbgtag, "Sending large msg size %zu > max size %zu",
+- mlen, ms->max_msg_sz);
++ MGMT_MSG_TRACE(dbgtag, "Sending large msg size %zu > max size %zu", mlen,
++ ms->max_msg_sz);
+
+ if (!ms->outs) {
+- MGMT_MSG_DBG(dbgtag, "creating new stream for msg len %zu", mlen);
++ MGMT_MSG_TRACE(dbgtag, "creating new stream for msg len %zu", mlen);
+ ms->outs = stream_new(MAX(ms->max_msg_sz, mlen));
+ } else if (mlen > ms->max_msg_sz && ms->outs->endp == 0) {
+ /* msg is larger than stream max size get a fit-to-size stream */
+- MGMT_MSG_DBG(dbgtag,
+- "replacing old stream with fit-to-size for msg len %zu",
+- mlen);
++ MGMT_MSG_TRACE(dbgtag, "replacing old stream with fit-to-size for msg len %zu",
++ mlen);
+ stream_free(ms->outs);
+ ms->outs = stream_new(mlen);
+ } else if (STREAM_WRITEABLE(ms->outs) < mlen) {
+- MGMT_MSG_DBG(dbgtag,
+- "enq existing stream len %zu and creating new stream for msg len %zu",
+- STREAM_WRITEABLE(ms->outs), mlen);
++ MGMT_MSG_TRACE(dbgtag,
++ "enq existing stream len %zu and creating new stream for msg len %zu",
++ STREAM_WRITEABLE(ms->outs), mlen);
+ stream_fifo_push(&ms->outq, ms->outs);
+ ms->outs = stream_new(MAX(ms->max_msg_sz, mlen));
+ } else {
+- MGMT_MSG_DBG(
+- dbgtag,
+- "using existing stream with avail %zu for msg len %zu",
+- STREAM_WRITEABLE(ms->outs), mlen);
++ MGMT_MSG_TRACE(dbgtag, "using existing stream with avail %zu for msg len %zu",
++ STREAM_WRITEABLE(ms->outs), mlen);
+ }
+ s = ms->outs;
+
+diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
+index 21ca54c3a4..763dbcc73f 100644
+--- a/lib/mgmt_msg_native.c
++++ b/lib/mgmt_msg_native.c
+@@ -69,9 +69,67 @@ size_t mgmt_msg_get_min_size(uint code)
+ return mgmt_msg_min_sizes[code];
+ }
+
+-int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id,
+- uint64_t req_id, bool short_circuit_ok,
+- int16_t error, const char *errfmt, va_list ap)
++const char *mgmt_msg_code_name(uint code)
++{
++ static char buf[48];
++
++ switch (code) {
++ case MGMT_MSG_CODE_ERROR:
++ return "ERROR";
++ case MGMT_MSG_CODE_GET_TREE:
++ return "GET_TREE";
++ case MGMT_MSG_CODE_TREE_DATA:
++ return "TREE_DATA";
++ case MGMT_MSG_CODE_GET_DATA:
++ return "GET_DATA";
++ case MGMT_MSG_CODE_NOTIFY:
++ return "NOTIFY";
++ case MGMT_MSG_CODE_EDIT:
++ return "EDIT";
++ case MGMT_MSG_CODE_EDIT_REPLY:
++ return "EDIT_REPLY";
++ case MGMT_MSG_CODE_RPC:
++ return "RPC";
++ case MGMT_MSG_CODE_RPC_REPLY:
++ return "RPC_REPLY";
++ case MGMT_MSG_CODE_NOTIFY_SELECT:
++ return "NOTIFY_SELECT";
++ case MGMT_MSG_CODE_SESSION_REQ:
++ return "SESSION_REQ";
++ case MGMT_MSG_CODE_SESSION_REPLY:
++ return "SESSION_REPLY";
++ case MGMT_MSG_CODE_LOCK:
++ return "LOCK";
++ case MGMT_MSG_CODE_LOCK_REPLY:
++ return "LOCK_REPLY";
++ case MGMT_MSG_CODE_COMMIT:
++ return "COMMIT";
++ case MGMT_MSG_CODE_COMMIT_REPLY:
++ return "COMMIT_REPLY";
++ case MGMT_MSG_CODE_SUBSCRIBE:
++ return "SUBSCRIBE";
++ case MGMT_MSG_CODE_TXN_REQ:
++ return "TXN_REQ";
++ case MGMT_MSG_CODE_TXN_REPLY:
++ return "TXN_REPLY";
++ case MGMT_MSG_CODE_CFG_REQ:
++ return "CFG_REQ";
++ case MGMT_MSG_CODE_CFG_REPLY:
++ return "CFG_REPLY";
++ case MGMT_MSG_CODE_CFG_APPLY_REQ:
++ return "CFG_APPLY_REQ";
++ case MGMT_MSG_CODE_CFG_APPLY_REPLY:
++ return "CFG_APPLY_REPLY";
++ default:
++ snprintf(buf, sizeof(buf), "UNKNOWN(%u)", code);
++ return buf;
++ }
++}
++
++
++int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id,
++ bool short_circuit_ok, int16_t error, const char *errfmt,
++ va_list ap)
+ {
+ struct mgmt_msg_error *msg;
+ char *errstr;
+diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
+index 99ce1e17fa..f6a0ce4d1a 100644
+--- a/lib/mgmt_msg_native.h
++++ b/lib/mgmt_msg_native.h
+@@ -215,6 +215,8 @@ DECLARE_MTYPE(MSG_NATIVE_COMMIT_REPLY);
+ #define MGMT_MSG_DATASTORE_CANDIDATE 2
+ #define MGMT_MSG_DATASTORE_OPERATIONAL 3
+
++const char *mgmt_msg_code_name(uint code);
++
+ /*
+ * Formats
+ */
+@@ -375,7 +377,13 @@ struct mgmt_msg_notify_data {
+ _Static_assert(sizeof(struct mgmt_msg_notify_data) == offsetof(struct mgmt_msg_notify_data, data),
+ "Size mismatch");
+
+-#define EDIT_FLAG_IMPLICIT_LOCK 0x01
++/*
++ * deprecated -- can simply do the locks as needed, if the session holds the
++ * locks then they aren't needed, otherwise an attempt to acquire them is made
++ * and if that fails the edit fails.
++ */
++#define EDIT_FLAG_IMPLICIT_LOCK 0x01
++/* also deprecated -- unneeded, if DS is candidate, then no "commit", if running then "commit" */
+ #define EDIT_FLAG_IMPLICIT_COMMIT 0x02
+
+ #define EDIT_OP_CREATE 0
+@@ -777,7 +785,7 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn,
+ * mgmt_msg_free_native_msg() - Free a native msg.
+ * @msg: pointer to message allocated by mgmt_msg_create_native_msg().
+ */
+-#define mgmt_msg_native_free_msg(msg) darr_free(msg)
++#define mgmt_msg_native_free_msg darr_free
+
+ /**
+ * mgmt_msg_native_get_msg_len() - Get the total length of the msg.
+diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
+index 81ed51e518..348cf2cc41 100644
+--- a/lib/northbound_cli.c
++++ b/lib/northbound_cli.c
+@@ -218,35 +218,12 @@ static void create_xpath_base_abs(struct vty *vty, char *xpath_base_abs,
+ static int _nb_cli_apply_changes(struct vty *vty, const char *xpath_base, bool clear_pending)
+ {
+ char xpath_base_abs[XPATH_MAXLEN] = {};
+- bool implicit_commit;
+
+ create_xpath_base_abs(vty, xpath_base_abs, sizeof(xpath_base_abs),
+ xpath_base);
+
+- if (vty_mgmt_should_process_cli_apply_changes(vty)) {
+- VTY_CHECK_XPATH;
+-
+- assert(vty->type != VTY_FILE);
+- /*
+- * The legacy user wanted to clear pending (i.e., perform a
+- * commit immediately) due to some non-yang compatible
+- * functionality. This new mgmtd code however, continues to send
+- * changes putting off the commit until XFRR_end is received
+- * (i.e., end-of-config-file). This should be fine b/c all
+- * conversions to mgmtd require full proper implementations.
+- */
+- if (!vty->num_cfg_changes)
+- return CMD_SUCCESS;
+-
+- implicit_commit = vty_needs_implicit_commit(vty);
+- if (vty_mgmt_send_config_data(vty, xpath_base_abs, implicit_commit) < 0) {
+- vty_out(vty, "%% Failed to apply configuration data.\n");
+- return CMD_WARNING_CONFIG_FAILED;
+- }
+- if (!implicit_commit)
+- ++vty->mgmt_num_pending_setcfg;
+- return CMD_SUCCESS;
+- }
++ if (vty->type != VTY_FILE && nb_cli_apply_changes_mgmt_cb)
++ return nb_cli_apply_changes_mgmt_cb(vty, xpath_base_abs);
+
+ return nb_cli_apply_changes_internal(vty, xpath_base_abs, clear_pending);
+ }
+@@ -330,20 +307,10 @@ int nb_cli_rpc(struct vty *vty, const char *xpath, struct lyd_node **output_p)
+ assert(err == LY_SUCCESS);
+ }
+
+- if (vty_mgmt_fe_enabled()) {
+- char *data = NULL;
+-
+- err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK);
+- assert(err == LY_SUCCESS);
+-
+- ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data);
+-
+- free(data);
++ if (nb_cli_rpc_mgmt_cb) {
++ ret = nb_cli_rpc_mgmt_cb(vty, xpath, input);
+ lyd_free_all(input);
+-
+- if (ret < 0)
+- return CMD_WARNING;
+- return CMD_SUCCESS;
++ return ret;
+ }
+
+ /* validate input tree to create implicit defaults */
+diff --git a/lib/vty.c b/lib/vty.c
+index b4e59a2a7b..41319afd0d 100644
+--- a/lib/vty.c
++++ b/lib/vty.c
+@@ -73,9 +73,14 @@ enum vty_event {
+
+ struct nb_config *vty_mgmt_candidate_config;
+
+-static struct mgmt_fe_client *mgmt_fe_client;
+-static bool mgmt_fe_connected;
+-static uint64_t mgmt_client_id_next;
++void (*vty_new_mgmt_cb)(struct vty *vty);
++void (*vty_close_mgmt_cb)(struct vty *vty);
++int (*vty_config_enter_mgmt_cb)(struct vty *vty, bool private_config, bool exclusive,
++ bool file_lock);
++void (*vty_config_node_exit_mgmt_cb)(struct vty *vty);
++int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs);
++int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input);
++
+
+ PREDECL_DLIST(vtyservs);
+
+@@ -128,66 +133,10 @@ static bool vty_log_commands_perm;
+ /* Vty incremental flush threshold size */
+ static const int VTY_MAX_INCREMENTAL_FLUSH = 1024 * 128;
+
+-char const *const mgmt_daemons[] = {
+- "zebra",
+-#ifdef HAVE_RIPD
+- "ripd",
+-#endif
+-#ifdef HAVE_RIPNGD
+- "ripngd",
+-#endif
+-#ifdef HAVE_STATICD
+- "staticd",
+-#endif
+-};
+-uint mgmt_daemons_count = array_size(mgmt_daemons);
+-
+-
+-static int vty_mgmt_lock_candidate_inline(struct vty *vty)
+-{
+- assert(!vty->mgmt_locked_candidate_ds);
+- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true);
+- return vty->mgmt_locked_candidate_ds ? 0 : -1;
+-}
+-
+-static int vty_mgmt_unlock_candidate_inline(struct vty *vty)
+-{
+- assert(vty->mgmt_locked_candidate_ds);
+- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true);
+- return vty->mgmt_locked_candidate_ds ? -1 : 0;
+-}
+-
+-static int vty_mgmt_lock_running_inline(struct vty *vty)
+-{
+- assert(!vty->mgmt_locked_running_ds);
+- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true);
+- return vty->mgmt_locked_running_ds ? 0 : -1;
+-}
+-
+-static int vty_mgmt_unlock_running_inline(struct vty *vty)
+-{
+- assert(vty->mgmt_locked_running_ds);
+- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true);
+- return vty->mgmt_locked_running_ds ? -1 : 0;
+-}
+-
+-void vty_mgmt_resume_response(struct vty *vty, int ret)
++void vty_resume_response(struct vty *vty, int ret)
+ {
+ uint8_t header[4] = {0, 0, 0, 0};
+
+- if (!vty->mgmt_req_pending_cmd) {
+- zlog_err(
+- "vty resume response called without mgmt_req_pending_cmd");
+- return;
+- }
+-
+- debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64
+- " with '%s'",
+- vty->mgmt_req_pending_cmd, vty->mgmt_session_id,
+- ret == CMD_SUCCESS ? "success" : "failed");
+-
+- vty->mgmt_req_pending_cmd = NULL;
+-
+ if (vty->type != VTY_FILE) {
+ header[3] = ret;
+ buffer_put(vty->obuf, header, 4);
+@@ -1724,17 +1673,8 @@ struct vty *vty_new(void)
+ new->max = VTY_BUFSIZ;
+ new->pass_fd = -1;
+
+- if (mgmt_fe_client) {
+- if (!mgmt_client_id_next)
+- mgmt_client_id_next++;
+- new->mgmt_client_id = mgmt_client_id_next++;
+- new->mgmt_session_id = 0;
+- mgmt_fe_create_client_session(
+- mgmt_fe_client, new->mgmt_client_id, (uintptr_t) new);
+- /* we short-circuit create the session so it must be set now */
+- assertf(new->mgmt_session_id != 0,
+- "Failed to create client session for VTY");
+- }
++ if (vty_new_mgmt_cb)
++ vty_new_mgmt_cb(new);
+
+ return new;
+ }
+@@ -2297,77 +2237,6 @@ void vty_pass_fd(struct vty *vty, int fd)
+ vty->pass_fd = fd;
+ }
+
+-bool mgmt_vty_read_configs(void)
+-{
+- char path[PATH_MAX];
+- struct vty *vty;
+- FILE *confp;
+- uint line_num = 0;
+- uint count = 0;
+- uint index;
+-
+- vty = vty_new();
+- vty->wfd = STDERR_FILENO;
+- vty->type = VTY_FILE;
+- vty->node = CONFIG_NODE;
+- vty->config = true;
+- vty->pending_allowed = true;
+-
+- vty->candidate_config = vty_shared_candidate_config;
+-
+- vty_mgmt_lock_candidate_inline(vty);
+- vty_mgmt_lock_running_inline(vty);
+-
+- for (index = 0; index < array_size(mgmt_daemons); index++) {
+- snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir,
+- mgmt_daemons[index]);
+-
+- confp = vty_open_config(path, config_default);
+- if (!confp)
+- continue;
+-
+- zlog_info("mgmtd: reading config file: %s", path);
+-
+- /* Execute configuration file */
+- line_num = 0;
+- (void)config_from_file(vty, confp, &line_num);
+- count++;
+-
+- fclose(confp);
+- }
+-
+- snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir);
+- confp = vty_open_config(path, config_default);
+- if (confp) {
+- zlog_info("mgmtd: reading config file: %s", path);
+-
+- line_num = 0;
+- (void)config_from_file(vty, confp, &line_num);
+- count++;
+-
+- fclose(confp);
+- }
+-
+- /* Conditionally unlock as the config file may have "exit"d early which
+- * would then have unlocked things.
+- */
+- if (vty->mgmt_locked_running_ds)
+- vty_mgmt_unlock_running_inline(vty);
+- if (vty->mgmt_locked_candidate_ds)
+- vty_mgmt_unlock_candidate_inline(vty);
+-
+- vty->pending_allowed = false;
+-
+- if (!count)
+- vty_close(vty);
+- else
+- vty_read_file_finish(vty, NULL);
+-
+- zlog_info("mgmtd: finished reading config files");
+-
+- return true;
+-}
+-
+ static void vtysh_read(struct event *thread)
+ {
+ int ret;
+@@ -2568,12 +2437,8 @@ void vty_close(struct vty *vty)
+ /* Drop out of configure / transaction if needed. */
+ vty_config_exit(vty);
+
+- if (mgmt_fe_client && vty->mgmt_session_id) {
+- debug_fe_client("closing vty session");
+- mgmt_fe_destroy_client_session(mgmt_fe_client,
+- vty->mgmt_client_id);
+- vty->mgmt_session_id = 0;
+- }
++ if (vty_close_mgmt_cb)
++ vty_close_mgmt_cb(vty);
+
+ /* Cancel threads.*/
+ event_cancel(&vty->t_read);
+@@ -2936,39 +2801,17 @@ bool vty_read_config(struct nb_config *config, const char *config_file,
+ int vty_config_enter(struct vty *vty, bool private_config, bool exclusive,
+ bool file_lock)
+ {
+- if (exclusive && !vty_mgmt_fe_enabled() &&
+- nb_running_lock(NB_CLIENT_CLI, vty)) {
++ int ret = CMD_SUCCESS;
++
++ if (vty_config_enter_mgmt_cb)
++ ret = vty_config_enter_mgmt_cb(vty, private_config, exclusive, file_lock);
++ else if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) {
+ vty_out(vty, "%% Configuration is locked by other client\n");
+- return CMD_WARNING;
++ ret = CMD_WARNING;
+ }
+
+- /*
+- * We only need to do a lock when reading a config file as we will be
+- * sending a batch of setcfg changes followed by a single commit
+- * message. For user interactive mode we are doing implicit commits
+- * those will obtain the lock (or not) when they try and commit.
+- */
+- if (file_lock && vty_mgmt_fe_enabled() && !private_config) {
+- if (vty_mgmt_lock_candidate_inline(vty)) {
+- vty_out(vty,
+- "%% Can't enter config; candidate datastore locked by another session\n");
+- return CMD_WARNING_CONFIG_FAILED;
+- }
+- if (vty_mgmt_lock_running_inline(vty)) {
+- vty_out(vty,
+- "%% Can't enter config; running datastore locked by another session\n");
+- vty_mgmt_unlock_candidate_inline(vty);
+- return CMD_WARNING_CONFIG_FAILED;
+- }
+- assert(vty->mgmt_locked_candidate_ds);
+- assert(vty->mgmt_locked_running_ds);
+-
+- /*
+- * As datastores are locked explicitly, we don't need implicit
+- * commits and should allow pending changes.
+- */
+- vty->pending_allowed = true;
+- }
++ if (ret != CMD_SUCCESS)
++ return ret;
+
+ vty->node = CONFIG_NODE;
+ vty->config = true;
+@@ -3026,11 +2869,8 @@ int vty_config_node_exit(struct vty *vty)
+
+ vty->pending_allowed = false;
+
+- if (vty->mgmt_locked_running_ds)
+- vty_mgmt_unlock_running_inline(vty);
+-
+- if (vty->mgmt_locked_candidate_ds)
+- vty_mgmt_unlock_candidate_inline(vty);
++ if (vty_config_node_exit_mgmt_cb)
++ vty_config_node_exit_mgmt_cb(vty);
+
+ /* Perform any pending commits. */
+ (void)nb_cli_pending_commit_check(vty);
+@@ -3539,575 +3379,6 @@ void vty_init_vtysh(void)
+ /* currently nothing to do, but likely to have future use */
+ }
+
+-
+-/*
+- * These functions allow for CLI handling to be placed inside daemons; however,
+- * currently they are only used by mgmtd, with mgmtd having each daemons CLI
+- * functionality linked into it. This design choice was taken for efficiency.
+- */
+-
+-static void vty_mgmt_server_connected(struct mgmt_fe_client *client,
+- uintptr_t usr_data, bool connected)
+-{
+- debug_fe_client("Got %sconnected %s MGMTD Frontend Server",
+- !connected ? "dis: " : "", !connected ? "from" : "to");
+-
+- /*
+- * We should not have any sessions for connecting or disconnecting case.
+- * The fe client library will delete all session on disconnect before
+- * calling us.
+- */
+- assert(mgmt_fe_client_session_count(client) == 0);
+-
+- mgmt_fe_connected = connected;
+-
+- /* Start or stop listening for vty connections */
+- if (connected)
+- frr_vty_serv_start(true);
+- else
+- frr_vty_serv_stop();
+-}
+-
+-/*
+- * A session has successfully been created for a vty.
+- */
+-static void vty_mgmt_session_notify(struct mgmt_fe_client *client,
+- uintptr_t usr_data, uint64_t client_id,
+- bool create, bool success,
+- uintptr_t session_id, uintptr_t session_ctx)
+-{
+- struct vty *vty;
+-
+- vty = (struct vty *)session_ctx;
+-
+- if (!success) {
+- zlog_err("%s session for client %" PRIu64 " failed!",
+- create ? "Creating" : "Destroying", client_id);
+- return;
+- }
+-
+- debug_fe_client("%s session for client %" PRIu64 " successfully",
+- create ? "Created" : "Destroyed", client_id);
+-
+- if (create) {
+- assert(session_id != 0);
+- vty->mgmt_session_id = session_id;
+- } else {
+- vty->mgmt_session_id = 0;
+- /* We may come here by way of vty_close() and short-circuits */
+- if (vty->status != VTY_CLOSE)
+- vty_close(vty);
+- }
+-}
+-
+-static void vty_mgmt_ds_lock_notified(struct mgmt_fe_client *client, uintptr_t usr_data,
+- uint64_t client_id, uintptr_t session_id,
+- uintptr_t session_ctx, uint64_t req_id, bool lock_ds,
+- bool success, enum mgmt_ds_id ds_id, char *errmsg_if_any)
+-{
+- struct vty *vty;
+- bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client);
+-
+- vty = (struct vty *)session_ctx;
+-
+- assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING);
+- if (!success)
+- zlog_err("%socking for DS %u failed, Err: '%s' vty %p",
+- lock_ds ? "L" : "Unl", ds_id, errmsg_if_any, vty);
+- else {
+- debug_fe_client("%socked DS %u successfully",
+- lock_ds ? "L" : "Unl", ds_id);
+- if (ds_id == MGMTD_DS_CANDIDATE)
+- vty->mgmt_locked_candidate_ds = lock_ds;
+- else
+- vty->mgmt_locked_running_ds = lock_ds;
+- }
+-
+- if (!is_short_circuit && vty->mgmt_req_pending_cmd) {
+- assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ"));
+- vty_mgmt_resume_response(vty,
+- success ? CMD_SUCCESS : CMD_WARNING);
+- }
+-}
+-
+-static void vty_mgmt_commit_config_result_notified(struct mgmt_fe_client *client,
+- uintptr_t usr_data, uint64_t client_id,
+- uintptr_t session_id, uintptr_t session_ctx,
+- uint64_t req_id, bool success,
+- enum mgmt_ds_id src_ds_id,
+- enum mgmt_ds_id dst_ds_id, bool validate_only,
+- bool unlock, char *errmsg_if_any)
+-{
+- struct vty *vty;
+-
+- vty = (struct vty *)session_ctx;
+-
+- if (!success) {
+- zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64
+- " failed, Error: '%s'",
+- client_id, errmsg_if_any ? errmsg_if_any : "Unknown");
+- vty_out(vty, "%% Configuration failed.\n\n");
+- if (errmsg_if_any)
+- vty_out(vty, "%s\n", errmsg_if_any);
+- } else {
+- debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64
+- " req-id %" PRIu64 " was successfull%s%s",
+- client_id, req_id, errmsg_if_any ? ": " : "",
+- errmsg_if_any ?: "");
+- if (!unlock && errmsg_if_any)
+- vty_out(vty, "MGMTD: %s\n", errmsg_if_any);
+- }
+-
+- if (unlock) {
+- /* we locked these when we sent the commit, unlock now */
+- vty_mgmt_unlock_candidate_inline(vty);
+- vty_mgmt_unlock_running_inline(vty);
+- }
+-
+- vty_mgmt_resume_response(vty, success ? CMD_SUCCESS
+- : CMD_WARNING_CONFIG_FAILED);
+-}
+-
+-static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf,
+- size_t count)
+-{
+- struct vty *vty = user_data;
+-
+- vty_out(vty, "%.*s", (int)count, (const char *)buf);
+- return count;
+-}
+-
+-static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format,
+- const struct ly_err_item *ei)
+-{
+-#if (LY_VERSION_MAJOR < 3)
+-#define data_path path
+-#else
+-#define data_path data_path
+-#endif
+- bool have_apptag = ei->apptag && ei->apptag[0] != 0;
+- bool have_path = ei->data_path && ei->data_path[0] != 0;
+- bool have_msg = ei->msg && ei->msg[0] != 0;
+- const char *severity = NULL;
+- const char *evalid = NULL;
+- const char *ecode = NULL;
+-#if (LY_VERSION_MAJOR < 3)
+- LY_ERR err = ei->no;
+-#else
+- LY_ERR err = ei->err;
+-#endif
+-
+- if (ei->level == LY_LLERR)
+- severity = "error";
+- else if (ei->level == LY_LLWRN)
+- severity = "warning";
+-
+- ecode = yang_ly_strerrcode(err);
+- if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS)
+- evalid = yang_ly_strvecode(ei->vecode);
+-
+- switch (format) {
+- case LYD_XML:
+- vty_out(vty,
+- "<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
+- vty_out(vty, "<error-type>application</error-type>");
+- if (severity)
+- vty_out(vty, "<error-severity>%s</error-severity>",
+- severity);
+- if (ecode)
+- vty_out(vty, "<error-code>%s</error-code>", ecode);
+- if (evalid)
+- vty_out(vty, "<error-validation>%s</error-validation>\n",
+- evalid);
+- if (have_path)
+- vty_out(vty, "<error-path>%s</error-path>\n",
+- ei->data_path);
+- if (have_apptag)
+- vty_out(vty, "<error-app-tag>%s</error-app-tag>\n",
+- ei->apptag);
+- if (have_msg)
+- vty_out(vty, "<error-message>%s</error-message>\n",
+- ei->msg);
+-
+- vty_out(vty, "</rpc-error>");
+- break;
+- case LYD_JSON:
+- vty_out(vty, "{ \"error-type\": \"application\"");
+- if (severity)
+- vty_out(vty, ", \"error-severity\": \"%s\"", severity);
+- if (ecode)
+- vty_out(vty, ", \"error-code\": \"%s\"", ecode);
+- if (evalid)
+- vty_out(vty, ", \"error-validation\": \"%s\"", evalid);
+- if (have_path)
+- vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path);
+- if (have_apptag)
+- vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag);
+- if (have_msg)
+- vty_out(vty, ", \"error-message\": \"%s\"", ei->msg);
+-
+- vty_out(vty, "}");
+- break;
+- case LYD_UNKNOWN:
+- case LYD_LYB:
+- default:
+- vty_out(vty, "%% error");
+- if (severity)
+- vty_out(vty, " severity: %s", severity);
+- if (evalid)
+- vty_out(vty, " invalid: %s", evalid);
+- if (have_path)
+- vty_out(vty, " path: %s", ei->data_path);
+- if (have_apptag)
+- vty_out(vty, " app-tag: %s", ei->apptag);
+- if (have_msg)
+- vty_out(vty, " msg: %s", ei->msg);
+- break;
+- }
+-#undef data_path
+-}
+-
+-static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format)
+-{
+- const struct ly_err_item *ei = ly_err_first(ly_native_ctx);
+- uint count;
+-
+- if (!ei)
+- return 0;
+-
+- if (format == LYD_JSON)
+- vty_out(vty, "\"ietf-restconf:errors\": [ ");
+-
+- for (count = 0; ei; count++, ei = ei->next) {
+- if (count)
+- vty_out(vty, ", ");
+- vty_out_yang_error(vty, format, ei);
+- }
+-
+- if (format == LYD_JSON)
+- vty_out(vty, " ]");
+-
+- ly_err_clean(ly_native_ctx, NULL);
+-
+- return count;
+-}
+-
+-
+-static int vty_mgmt_get_tree_result_notified(struct mgmt_fe_client *client, uintptr_t user_data,
+- uint64_t client_id, uint64_t session_id,
+- uintptr_t session_ctx, uint64_t req_id,
+- enum mgmt_ds_id ds_id, LYD_FORMAT result_type,
+- void *result, size_t len, int partial_error)
+-{
+- struct vty *vty;
+- struct lyd_node *dnode;
+- int ret = CMD_SUCCESS;
+- LY_ERR err;
+-
+- vty = (struct vty *)session_ctx;
+-
+- debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64
+- " req-id %" PRIu64,
+- partial_error ? "partially " : "", client_id, req_id);
+-
+- assert(result_type == LYD_LYB ||
+- result_type == vty->mgmt_req_pending_data);
+-
+- if (vty->mgmt_req_pending_data == LYD_XML && partial_error)
+- vty_out(vty,
+- "<!-- some errors occurred gathering results -->\n");
+-
+- if (result_type == LYD_LYB) {
+- /*
+- * parse binary into tree and print in the specified format
+- */
+- result_type = vty->mgmt_req_pending_data;
+-
+- err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0,
+- &dnode);
+- if (!err)
+- err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode,
+- result_type, LYD_PRINT_WITHSIBLINGS);
+- lyd_free_all(dnode);
+-
+- if (vty_out_yang_errors(vty, result_type) || err)
+- ret = CMD_WARNING;
+- } else {
+- /*
+- * Print the in-format result
+- */
+- assert(result_type == LYD_XML || result_type == LYD_JSON);
+- vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result);
+- }
+-
+- vty_mgmt_resume_response(vty, ret);
+-
+- return 0;
+-}
+-
+-static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client,
+- uintptr_t user_data,
+- uint64_t client_id, uint64_t session_id,
+- uintptr_t session_ctx, uint64_t req_id,
+- const char *xpath)
+-{
+- struct vty *vty = (struct vty *)session_ctx;
+-
+- debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64
+- " was successful, xpath: %s",
+- client_id, req_id, xpath);
+-
+- vty_mgmt_resume_response(vty, CMD_SUCCESS);
+-
+- return 0;
+-}
+-
+-static int vty_mgmt_rpc_result_notified(struct mgmt_fe_client *client,
+- uintptr_t user_data, uint64_t client_id,
+- uint64_t session_id,
+- uintptr_t session_ctx, uint64_t req_id,
+- const char *result)
+-{
+- struct vty *vty = (struct vty *)session_ctx;
+-
+- debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64
+- " was successful",
+- client_id, req_id);
+-
+- if (result)
+- vty_out(vty, "%s\n", result);
+-
+- vty_mgmt_resume_response(vty, CMD_SUCCESS);
+-
+- return 0;
+-}
+-
+-static int vty_mgmt_error_notified(struct mgmt_fe_client *client,
+- uintptr_t user_data, uint64_t client_id,
+- uint64_t session_id, uintptr_t session_ctx,
+- uint64_t req_id, int error,
+- const char *errstr)
+-{
+- struct vty *vty = (struct vty *)session_ctx;
+- const char *cname = mgmt_fe_client_name(client);
+-
+- if (!vty->mgmt_req_pending_cmd) {
+- debug_fe_client("Error with no pending command: %d returned for client %s 0x%" PRIx64
+- " session-id %" PRIu64 " req-id %" PRIu64
+- "error-str %s",
+- error, cname, client_id, session_id, req_id,
+- errstr);
+- vty_out(vty,
+- "%% Error %d from MGMTD for %s with no pending command: %s\n",
+- error, cname, errstr);
+- return CMD_WARNING;
+- }
+-
+- debug_fe_client("Error %d returned for client %s 0x%" PRIx64
+- " session-id %" PRIu64 " req-id %" PRIu64 "error-str %s",
+- error, cname, client_id, session_id, req_id, errstr);
+-
+- vty_out(vty, "%% %s (for %s, client %s)\n", errstr,
+- vty->mgmt_req_pending_cmd, cname);
+-
+- vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS);
+-
+- return 0;
+-}
+-
+-static struct mgmt_fe_client_cbs mgmt_cbs = {
+- .client_connect_notify = vty_mgmt_server_connected,
+- .client_session_notify = vty_mgmt_session_notify,
+- .lock_ds_notify = vty_mgmt_ds_lock_notified,
+- .commit_config_notify = vty_mgmt_commit_config_result_notified,
+- .get_tree_notify = vty_mgmt_get_tree_result_notified,
+- .edit_notify = vty_mgmt_edit_result_notified,
+- .rpc_notify = vty_mgmt_rpc_result_notified,
+- .error_notify = vty_mgmt_error_notified,
+-
+-};
+-
+-void vty_init_mgmt_fe(void)
+-{
+- char name[40];
+-
+- assert(vty_master);
+- assert(!mgmt_fe_client);
+- snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(),
+- (long)getpid());
+- mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, vty_master);
+- assert(mgmt_fe_client);
+-}
+-
+-bool vty_mgmt_fe_enabled(void)
+-{
+- return mgmt_fe_client && mgmt_fe_connected;
+-}
+-
+-bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty)
+-{
+- return vty->type != VTY_FILE && vty_mgmt_fe_enabled();
+-}
+-
+-int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok)
+-{
+- assert(mgmt_fe_client);
+- assert(vty->mgmt_session_id);
+-
+- vty->mgmt_req_id++;
+- if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id,
+- vty->mgmt_req_id, ds_id, lock, scok)) {
+- zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64,
+- lock ? "" : "UN", vty->mgmt_req_id);
+- vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n",
+- lock ? "" : "UN");
+- return -1;
+- }
+-
+- if (!scok)
+- vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ";
+-
+- return 0;
+-}
+-
+-int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base,
+- bool implicit_commit)
+-{
+- char err_buf[BUFSIZ];
+- bool error = false;
+-
+- /* ADD this: candidate is locked or error */
+-
+- if (implicit_commit) {
+- assert(vty->mgmt_client_id && vty->mgmt_session_id);
+- if (vty_mgmt_lock_candidate_inline(vty)) {
+- vty_out(vty, "%% could not lock candidate DS\n");
+- return CMD_WARNING_CONFIG_FAILED;
+- } else if (vty_mgmt_lock_running_inline(vty)) {
+- vty_out(vty, "%% could not lock running DS\n");
+- vty_mgmt_unlock_candidate_inline(vty);
+- return CMD_WARNING_CONFIG_FAILED;
+- }
+- }
+-
+- nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes,
+- vty->num_cfg_changes, xpath_base, false, err_buf,
+- sizeof(err_buf), &error);
+- if (error) {
+- /*
+- * Failure to edit the candidate configuration should never
+- * happen in practice, unless there's a bug in the code. When
+- * that happens, log the error but otherwise ignore it.
+- */
+- vty_out(vty, "%% Couldn't apply changes: %s", err_buf);
+-
+-#if 0 // or do we expect the caller to do this?
+- /* the candidate is no longer valid so restore it */
+- nb_config_replace(vty->candidate_config, running_config, true);
+-#endif
+-error:
+- if (implicit_commit) {
+- vty_mgmt_unlock_running_inline(vty);
+- vty_mgmt_unlock_candidate_inline(vty);
+- }
+- return CMD_WARNING_CONFIG_FAILED;
+- }
+-
+- if (!implicit_commit)
+- return CMD_SUCCESS;
+-
+- assert(vty->mgmt_client_id && vty->mgmt_session_id);
+- if (vty_mgmt_send_commit_config(vty, false, false, true) < 0)
+- goto error;
+-
+- return CMD_SUCCESS;
+-}
+-
+-int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock)
+-{
+- if (mgmt_fe_client && vty->mgmt_session_id) {
+- vty->mgmt_req_id++;
+- if (mgmt_fe_send_commitcfg_req(mgmt_fe_client, vty->mgmt_session_id,
+- vty->mgmt_req_id, MGMTD_DS_CANDIDATE,
+- MGMTD_DS_RUNNING, validate_only, abort, unlock)) {
+- zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64,
+- vty->mgmt_req_id);
+- vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n");
+- return -1;
+- }
+-
+- vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ";
+- vty->mgmt_num_pending_setcfg = 0;
+- }
+-
+- return 0;
+-}
+-
+-int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore,
+- LYD_FORMAT result_type, uint8_t flags,
+- uint8_t defaults, const char *xpath)
+-{
+- LYD_FORMAT intern_format = result_type;
+-
+- vty->mgmt_req_id++;
+-
+- if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id,
+- vty->mgmt_req_id, datastore,
+- intern_format, flags, defaults, xpath)) {
+- zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64
+- " req-id %" PRIu64 ".",
+- vty->mgmt_session_id, vty->mgmt_req_id);
+- vty_out(vty, "Failed to send GET-DATA to MGMTD!\n");
+- return -1;
+- }
+-
+- vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ";
+- vty->mgmt_req_pending_data = result_type;
+-
+- return 0;
+-}
+-
+-int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore,
+- LYD_FORMAT request_type, uint8_t flags,
+- uint8_t operation, const char *xpath,
+- const char *data)
+-{
+- vty->mgmt_req_id++;
+-
+- if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id,
+- vty->mgmt_req_id, datastore, request_type,
+- flags, operation, xpath, data)) {
+- zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64
+- " req-id %" PRIu64 ".",
+- vty->mgmt_session_id, vty->mgmt_req_id);
+- vty_out(vty, "Failed to send EDIT to MGMTD!\n");
+- return -1;
+- }
+-
+- vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ";
+-
+- return 0;
+-}
+-
+-int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type,
+- const char *xpath, const char *data)
+-{
+- vty->mgmt_req_id++;
+-
+- if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id,
+- vty->mgmt_req_id, request_type, xpath, data)) {
+- zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64
+- " req-id %" PRIu64 ".",
+- vty->mgmt_session_id, vty->mgmt_req_id);
+- vty_out(vty, "Failed to send RPC to MGMTD!\n");
+- return -1;
+- }
+-
+- vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ";
+-
+- return 0;
+-}
+-
+ /* Install vty's own commands like `who' command. */
+ void vty_init(struct event_loop *master_thread, bool do_command_logging)
+ {
+@@ -4173,10 +3444,5 @@ void vty_terminate(void)
+ vtys_fini(vtysh_sessions);
+ vtys_init(vtysh_sessions);
+
+- if (mgmt_fe_client) {
+- mgmt_fe_client_destroy(mgmt_fe_client);
+- mgmt_fe_client = NULL;
+- }
+-
+ vty_serv_stop();
+ }
+diff --git a/lib/vty.h b/lib/vty.h
+index 83185c7b8f..5caa412ef7 100644
+--- a/lib/vty.h
++++ b/lib/vty.h
+@@ -415,30 +415,6 @@ extern void vty_stdio_suspend(void);
+ extern void vty_stdio_resume(void);
+ extern void vty_stdio_close(void);
+
+-extern void vty_init_mgmt_fe(void);
+-extern bool vty_mgmt_fe_enabled(void);
+-extern bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty);
+-
+-extern bool mgmt_vty_read_configs(void);
+-extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base,
+- bool implicit_commit);
+-extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock);
+-extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore,
+- LYD_FORMAT result_type, uint8_t flags,
+- uint8_t defaults, const char *xpath);
+-extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore,
+- LYD_FORMAT request_type, uint8_t flags,
+- uint8_t operation, const char *xpath,
+- const char *data);
+-extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type,
+- const char *xpath, const char *data);
+-extern int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok);
+-extern void vty_mgmt_resume_response(struct vty *vty, int ret);
+-
+-static inline bool vty_needs_implicit_commit(const struct vty *vty)
+-{
+- return frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed;
+-}
+
+ /* Applications can check vty status */
+ static inline bool vty_is_closed(const struct vty *vty)
+@@ -447,6 +423,23 @@ static inline bool vty_is_closed(const struct vty *vty)
+ vty->wfd < 0);
+ }
+
++/*
++ * Semi-private APIs for use in mgmtd-vty code
++ */
++extern void vty_resume_response(struct vty *vty, int ret);
++
++/* --------------------------------------------------- */
++/* Callbacks for Mgmtd front-end CLI vty modifications */
++/* --------------------------------------------------- */
++extern void (*vty_new_mgmt_cb)(struct vty *vty);
++extern void (*vty_close_mgmt_cb)(struct vty *vty);
++extern int (*vty_config_enter_mgmt_cb)(struct vty *vty, bool private_config, bool exclusive,
++ bool file_lock);
++extern void (*vty_config_node_exit_mgmt_cb)(struct vty *vty);
++extern int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs);
++extern int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input);
++
++
+ #ifdef __cplusplus
+ }
+ #endif
+diff --git a/mgmtd/mgmt.c b/mgmtd/mgmt.c
+index 7f8a142171..d882fa2ed0 100644
+--- a/mgmtd/mgmt.c
++++ b/mgmtd/mgmt.c
+@@ -57,7 +57,7 @@ void mgmt_init(void)
+ mgmt_history_init();
+
+ /* Initialize MGMTD Transaction module */
+- mgmt_txn_init(mm, mm->master);
++ mgmt_txn_init();
+
+ /* Initialize the MGMTD Frontend Adapter Module */
+ mgmt_fe_adapter_init(mm->master);
+@@ -66,7 +66,7 @@ void mgmt_init(void)
+ * Initialize the CLI frontend client -- this queues an event for the
+ * client to short-circuit connect to the server (ourselves).
+ */
+- vty_init_mgmt_fe();
++ vty_mgmt_init();
+
+ /*
+ * MGMTD VTY commands installation -- the frr lib code will queue an
+@@ -91,9 +91,9 @@ void mgmt_init(void)
+ void mgmt_terminate(void)
+ {
+ mgmt_be_client_destroy(mgmt_be_client);
++ mgmt_txn_destroy();
+ mgmt_fe_adapter_destroy();
+ mgmt_be_adapter_destroy();
+- mgmt_txn_destroy();
+ mgmt_history_destroy();
+ mgmt_ds_destroy();
+ }
+diff --git a/mgmtd/mgmt.h b/mgmtd/mgmt.h
+index 665e8d8fd8..592b993ad5 100644
+--- a/mgmtd/mgmt.h
++++ b/mgmtd/mgmt.h
+@@ -33,7 +33,7 @@ extern struct debug mgmt_debug_txn;
+ #define MGMT_DEBUG_FE_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)
+ #define MGMT_DEBUG_TXN_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_tx, DEBUG_MODE_ALL)
+
+-struct mgmt_txn_ctx;
++struct mgmt_txn;
+
+ /*
+ * MGMTD master for system wide configurations and variables.
+@@ -44,16 +44,6 @@ struct mgmt_master {
+ /* How big should we set the socket buffer size */
+ uint32_t socket_buffer;
+
+- /* The single instance of config transaction allowed at any time */
+- struct mgmt_txns_head txn_list;
+-
+- /* Map of Transactions and its ID */
+- struct hash *txn_hash;
+- uint64_t next_txn_id;
+-
+- /* The single instance of config transaction allowed at any time */
+- struct mgmt_txn_ctx *cfg_txn;
+-
+ /* Datastores */
+ struct mgmt_ds_ctx *running_ds;
+ struct mgmt_ds_ctx *candidate_ds;
+@@ -66,6 +56,7 @@ struct mgmt_master {
+ struct mgmt_cmt_infos_head cmts; /* List of last 10 commits executed. */
+ };
+
++extern struct frr_daemon_info *mgmt_daemon_info;
+ extern struct mgmt_master *mm;
+
+ /* Inline functions */
+@@ -101,4 +92,24 @@ extern void mgmt_master_init(struct event_loop *master, const int buffer_size);
+ extern void mgmt_init(void);
+ extern void mgmt_vty_init(void);
+
++/*
++ * mgmt vty
++ */
++extern void vty_mgmt_init(void);
++extern void vty_mgmt_terminate(void);
++
++extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort,
++ bool unlock);
++extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type,
++ uint8_t flags, uint8_t defaults, const char *xpath);
++extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type,
++ uint8_t flags, uint8_t operation, const char *xpath,
++ const char *data);
++extern void vty_mgmt_resume_response(struct vty *vty, int ret);
++
++
++extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, bool implicit_commit);
++extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, const char *xpath,
++ const char *data);
++
+ #endif /* _FRR_MGMTD_H */
+diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c
+index 6d0e303c9a..ff31d29051 100644
+--- a/mgmtd/mgmt_be_adapter.c
++++ b/mgmtd/mgmt_be_adapter.c
+@@ -4,7 +4,7 @@
+ *
+ * Copyright (C) 2021 Vmware, Inc.
+ * Pushpasis Sarkar <spushpasis@vmware.com>
+- * Copyright (c) 2023, LabN Consulting, L.L.C.
++ * Copyright (c) 2023-2025, LabN Consulting, L.L.C.
+ */
+
+ #include <zebra.h>
+@@ -22,15 +22,35 @@
+ #include "mgmtd/mgmt_be_adapter.h"
+
+ #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_be, "BE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__)
++#define _log_warn(fmt, ...) zlog_warn("BE-ADAPTER: %s: WARNING: " fmt, __func__, ##__VA_ARGS__)
+ #define _log_err(fmt, ...) zlog_err("BE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+
+-#define FOREACH_ADAPTER_IN_LIST(adapter) \
+- frr_each_safe (mgmt_be_adapters, &mgmt_be_adapters, (adapter))
+
+ /* ---------- */
+-/* Client IDs */
++/* Prototypes */
+ /* ---------- */
+
++/* Forward declarations */
++static void be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter);
++
++static void be_adapter_delete(struct mgmt_be_client_adapter *adapter);
++
++static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name);
++
++
++/* --------- */
++/* Constants */
++/* --------- */
++
++/*
++ * Client IDs
++ */
++
++/*
++ * NOTE: This mapping is more trouble than it's worth. Just convert to a dynamic
++ * allocation as backends subscribe/register.
++ */
++
+ const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = {
+ [MGMTD_BE_CLIENT_ID_TESTC] = "mgmtd-testc", /* always first */
+ [MGMTD_BE_CLIENT_ID_MGMTD] = "mgmtd", /* loopback */
+@@ -47,9 +67,15 @@ const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = {
+ [MGMTD_BE_CLIENT_ID_MAX] = "Unknown/Invalid",
+ };
+
+-/* ------------- */
+-/* XPATH MAPPING */
+-/* ------------- */
++
++/* ------------------------------- */
++/* Const XPath Mappings (for init) */
++/* ------------------------------- */
++
++/*
++ * NOTE: These mappings are more trouble than they are worth. Just convert to
++ * use the subscribe message exclusively.
++ */
+
+ /*
+ * Mapping of YANG XPath prefixes to their corresponding backend clients.
+@@ -228,6 +254,10 @@ static const char *const *be_client_rpc_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
+ [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_rpc_xpaths,
+ };
+
++/* ---------------- */
++/* Global Variables */
++/* ---------------- */
++
+ /*
+ * We would like to have a better ADT than one with O(n) comparisons
+ *
+@@ -245,7 +275,7 @@ static struct mgmt_be_xpath_map *be_rpc_xpath_map;
+ static struct event_loop *mgmt_loop;
+ static struct msg_server mgmt_be_server = {.fd = -1};
+
+-static struct mgmt_be_adapters_head mgmt_be_adapters;
++LIST_HEAD(be_adapter_list_head, mgmt_be_client_adapter) be_adapters;
+
+ static struct mgmt_be_client_adapter
+ *mgmt_be_adapters_by_id[MGMTD_BE_CLIENT_ID_MAX];
+@@ -259,14 +289,11 @@ static const char *const mgmtd_config_xpaths[] = {
+ };
+
+
+-/* Forward declarations */
+-static void
+-mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter);
+-
+-static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id,
+- enum mgmt_be_xpath_subscr_type type);
++/* ---------------- */
++/* Lookup Functions */
++/* ---------------- */
+
+-const char *mgmt_be_client_id2name(enum mgmt_be_client_id id)
++static const char *be_adapter_id_name(enum mgmt_be_client_id id)
+ {
+ if (id > MGMTD_BE_CLIENT_ID_MAX)
+ return "invalid client id";
+@@ -278,43 +305,173 @@ static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name)
+ enum mgmt_be_client_id id;
+
+ FOREACH_MGMTD_BE_CLIENT_ID (id) {
+- if (!strncmp(mgmt_be_client_names[id], name,
+- MGMTD_CLIENT_NAME_MAX_LEN))
++ if (!strncmp(mgmt_be_client_names[id], name, MGMTD_CLIENT_NAME_MAX_LEN))
+ return id;
+ }
+
+ return MGMTD_BE_CLIENT_ID_MAX;
+ }
+
+-static struct mgmt_be_client_adapter *
+-mgmt_be_find_adapter_by_fd(int conn_fd)
++static struct mgmt_be_client_adapter *mgmt_be_find_adapter_by_fd(int conn_fd)
+ {
+ struct mgmt_be_client_adapter *adapter;
+
+- FOREACH_ADAPTER_IN_LIST (adapter) {
++ LIST_FOREACH (adapter, &be_adapters, link)
+ if (adapter->conn->fd == conn_fd)
+ return adapter;
++ return NULL;
++}
++
++struct mgmt_be_client_adapter *mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id)
++{
++ return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL);
++}
++
++
++/* ======================= */
++/* XPath Mapping Functions */
++/* ======================= */
++
++/*
++ * Check if either path or xpath is a prefix of the other. Before checking the
++ * xpath is converted to a regular path string (e..g, removing key value
++ * specifiers).
++ */
++static bool mgmt_be_xpath_prefix(const char *path, const char *xpath)
++{
++ int xc, pc;
++
++ while ((xc = *xpath++)) {
++ if (xc == '[') {
++ xpath = frrstr_skip_over_char(xpath, ']');
++ if (!xpath)
++ return false;
++ continue;
++ }
++ pc = *path++;
++ if (!pc)
++ return true;
++ if (pc != xc)
++ return false;
+ }
++ return true;
++}
+
+- return NULL;
++/*
++ * Get the mask of clients interested in an xpath.
++ */
++uint64_t mgmt_be_interested_clients(const char *xpath, enum mgmt_be_xpath_subscr_type type)
++{
++ struct mgmt_be_xpath_map *maps = NULL, *map;
++ uint64_t clients = 0;
++ bool wild_root;
++
++ switch (type) {
++ case MGMT_BE_XPATH_SUBSCR_TYPE_CFG:
++ maps = be_cfg_xpath_map;
++ break;
++ case MGMT_BE_XPATH_SUBSCR_TYPE_OPER:
++ maps = be_oper_xpath_map;
++ break;
++ case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF:
++ maps = be_notif_xpath_map;
++ break;
++ case MGMT_BE_XPATH_SUBSCR_TYPE_RPC:
++ maps = be_rpc_xpath_map;
++ break;
++ }
++
++ /* wild_root will select all clients that advertise op-state */
++ wild_root = !strcmp(xpath, "/") || !strcmp(xpath, "/*");
++ darr_foreach_p (maps, map)
++ if (wild_root || mgmt_be_xpath_prefix(map->xpath_prefix, xpath))
++ clients |= map->clients;
++
++ _dbg("xpath: '%s' subscribed clients: 0x%Lx", xpath, clients);
++
++ return clients;
+ }
+
+-static struct mgmt_be_client_adapter *
+-mgmt_be_find_adapter_by_name(const char *name)
++/*
++ * Test for interest by mgmtd in xpath.
++ *
++ * Mgmtd handles it's own config directly vs as a backend client of
++ * itself. This function supports that.
++ */
++bool mgmt_is_mgmtd_interested(const char *xpath)
+ {
+- struct mgmt_be_client_adapter *adapter;
++ const char *const *match = mgmtd_config_xpaths;
++ const char *const *ematch = match + array_size(mgmtd_config_xpaths);
+
+- FOREACH_ADAPTER_IN_LIST (adapter) {
+- if (!strncmp(adapter->name, name, sizeof(adapter->name)))
+- return adapter;
++ for (; match < ematch; match++) {
++ if (mgmt_be_xpath_prefix(*match, xpath)) {
++ _dbg("mgmtd: subscribed to %s", xpath);
++ return true;
++ }
++ }
++ return false;
++}
++
++
++/*
++ * This function is inefficient. For each xpath it walks the global map list.
++ * We should keep a separate per-client map and use that here.
++ *
++ * NOTE: Fix this when removing the global constant maps used for bootstrapping.
++ */
++static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id,
++ enum mgmt_be_xpath_subscr_type type)
++{
++ uint64_t clients;
++
++ assert(id < MGMTD_BE_CLIENT_ID_MAX);
++
++ _dbg("Checking client: %s for xpath: '%s'", be_adapter_id_name(id), xpath);
++
++ clients = mgmt_be_interested_clients(xpath, type);
++ if (IS_IDBIT_SET(clients, id)) {
++ _dbg("client: %s: interested", be_adapter_id_name(id));
++ return true;
+ }
+
+- return NULL;
++ _dbg("client: %s: not interested", be_adapter_id_name(id));
++ return false;
++}
++
++/*
++ * Get full config changes for adapter.
++ *
++ * Walk the entire running config building a set of create-changes to send to
++ * the adapter and return a set of config changes.
++ */
++struct nb_config_cbs mgmt_be_adapter_get_config(struct mgmt_be_client_adapter *adapter)
++{
++ struct nb_config_cbs changes = { 0 };
++ const struct lyd_node *root, *dnode;
++ uint32_t seq = 0;
++ char *xpath;
++
++ LY_LIST_FOR (running_config->dnode, root) {
++ LYD_TREE_DFS_BEGIN (root, dnode) {
++ if (lysc_is_key(dnode->schema))
++ goto walk_cont;
++
++ xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
++ if (be_is_client_interested(xpath, adapter->id,
++ MGMT_BE_XPATH_SUBSCR_TYPE_CFG))
++ nb_config_diff_add_change(&changes, NB_CB_CREATE, &seq, dnode);
++ else
++ LYD_TREE_DFS_continue = 1; /* skip any subtree */
++ free(xpath);
++walk_cont:
++ LYD_TREE_DFS_END(root, dnode);
++ }
++ }
++ return changes;
+ }
+
+-static void mgmt_register_client_xpath(enum mgmt_be_client_id id,
+- const char *xpath,
+- enum mgmt_be_xpath_subscr_type type)
++static void be_adapter_register_client_xpath(enum mgmt_be_client_id id, const char *xpath,
++ enum mgmt_be_xpath_subscr_type type)
+ {
+ struct mgmt_be_xpath_map **maps, *map;
+
+@@ -337,7 +494,7 @@ static void mgmt_register_client_xpath(enum mgmt_be_client_id id,
+
+ darr_foreach_p (*maps, map) {
+ if (!strcmp(xpath, map->xpath_prefix)) {
+- map->clients |= (1u << id);
++ SET_IDBIT(map->clients, id);
+ return;
+ }
+ }
+@@ -348,9 +505,15 @@ static void mgmt_register_client_xpath(enum mgmt_be_client_id id,
+ }
+
+ /*
+- * initial the combined maps from per client maps
++ * Use the global per-client constants to initialize the xpath maps.
++ *
++ * NOTE: This system was used to bootstrap the mgmtd work, and has proved to add
++ * complexity to the process converting daemons to use mgmtd with no real
++ * performance requirement, the daemon can send it's xpath regsitrations in it's
++ * subscribe message so we should just use that. Please convert to do that and
++ * remove this function at first convenience.
+ */
+-static void mgmt_be_xpath_map_init(void)
++static void be_adapter_xpath_maps_init(void)
+ {
+ enum mgmt_be_client_id id;
+ const char *const *init;
+@@ -361,29 +524,26 @@ static void mgmt_be_xpath_map_init(void)
+ /* Initialize the common config init map */
+ for (init = be_client_config_xpaths[id]; init && *init; init++) {
+ _dbg(" - CFG XPATH: '%s'", *init);
+- mgmt_register_client_xpath(id, *init,
+- MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
++ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
+ }
+
+ /* Initialize the common oper init map */
+ for (init = be_client_oper_xpaths[id]; init && *init; init++) {
+ _dbg(" - OPER XPATH: '%s'", *init);
+- mgmt_register_client_xpath(id, *init,
+- MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
++ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ }
+
+ /* Initialize the common NOTIF init map */
+ for (init = be_client_notif_xpaths[id]; init && *init; init++) {
+ _dbg(" - NOTIF XPATH: '%s'", *init);
+- mgmt_register_client_xpath(id, *init,
+- MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF);
++ be_adapter_register_client_xpath(id, *init,
++ MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF);
+ }
+
+ /* Initialize the common RPC init map */
+ for (init = be_client_rpc_xpaths[id]; init && *init; init++) {
+ _dbg(" - RPC XPATH: '%s'", *init);
+- mgmt_register_client_xpath(id, *init,
+- MGMT_BE_XPATH_SUBSCR_TYPE_RPC);
++ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_RPC);
+ }
+ }
+
+@@ -393,7 +553,10 @@ static void mgmt_be_xpath_map_init(void)
+ _dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map));
+ }
+
+-static void mgmt_be_xpath_map_cleanup(void)
++/*
++ * Cleanup the xpath maps.
++ */
++static void be_adapter_xpath_maps_cleanup(void)
+ {
+ struct mgmt_be_xpath_map *map;
+
+@@ -415,138 +578,32 @@ static void mgmt_be_xpath_map_cleanup(void)
+ }
+
+
++/* ============================== */
++/* Backend Message (API) Handling */
++/* ============================== */
++
+ /*
+- * Check if either path or xpath is a prefix of the other. Before checking the
+- * xpath is converted to a regular path string (e..g, removing key value
+- * specifiers).
++ * The TXN module is the primary producer/consumer of messages to/from backend
++ * clients so that is where you will find most of the backend message handling
++ * functions.
+ */
+-static bool mgmt_be_xpath_prefix(const char *path, const char *xpath)
+-{
+- int xc, pc;
+-
+- while ((xc = *xpath++)) {
+- if (xc == '[') {
+- xpath = frrstr_skip_over_char(xpath, ']');
+- if (!xpath)
+- return false;
+- continue;
+- }
+- pc = *path++;
+- if (!pc)
+- return true;
+- if (pc != xc)
+- return false;
+- }
+- return true;
+-}
+-
+-static void mgmt_be_adapter_delete(struct mgmt_be_client_adapter *adapter)
+-{
+- _dbg("deleting client adapter '%s'", adapter->name);
+-
+- /*
+- * Notify about disconnect for appropriate cleanup
+- */
+- mgmt_txn_notify_be_adapter_conn(adapter, false);
+- if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) {
+- mgmt_be_adapters_by_id[adapter->id] = NULL;
+- adapter->id = MGMTD_BE_CLIENT_ID_MAX;
+- }
+-
+- assert(adapter->refcount == 1);
+- mgmt_be_adapter_unlock(&adapter);
+-}
+-
+-static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn)
+-{
+- struct mgmt_be_client_adapter *adapter = conn->user;
+-
+- _dbg("notify disconnect for client adapter '%s'", adapter->name);
+-
+- mgmt_be_adapter_delete(adapter);
+-
+- return 0;
+-}
+-
+-static void
+-mgmt_be_adapter_cleanup_old_conn(struct mgmt_be_client_adapter *adapter)
+-{
+- struct mgmt_be_client_adapter *old;
+
+- FOREACH_ADAPTER_IN_LIST (old) {
+- if (old != adapter &&
+- !strncmp(adapter->name, old->name, sizeof(adapter->name))) {
+- /*
+- * We have a Zombie lingering around
+- */
+- _dbg("Client '%s' (FD:%d) seems to have reconnected. Removing old connection (FD:%d)!",
+- adapter->name, adapter->conn->fd, old->conn->fd);
+- /* this will/should delete old */
+- msg_conn_disconnect(old->conn, false);
+- }
+- }
+-}
+-
+-int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, bool create)
+-{
+- struct mgmt_msg_txn_req *msg;
+- int ret;
+-
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_req, 0, MTYPE_MSG_NATIVE_TXN_REQ);
+- msg->code = MGMT_MSG_CODE_TXN_REQ;
+- msg->refer_id = txn_id;
+- msg->create = create;
+-
+- _dbg("Sending TXN_REQ to '%s' to %s txn-id: %Lu", adapter->name,
+- create ? "create" : "delete", txn_id);
+-
+- ret = mgmt_msg_native_send_msg(adapter->conn, msg, false);
+- mgmt_msg_native_free_msg(msg);
+- return ret;
+-}
+-
+-int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, struct mgmt_msg_cfg_req *msg)
++int mgmt_be_adapter_send(struct mgmt_be_client_adapter *adapter, void *_msg)
+ {
++ struct mgmt_msg_header *msg = (struct mgmt_msg_header *)_msg;
++ uint64_t txn_id = msg->refer_id;
+ int ret;
+
+- _dbg("Sending CFG_REQ to '%s' txn-id: %Lu req-id: %Lu", adapter->name, msg->refer_id,
+- msg->req_id);
++ _dbg("Sending %s to '%s' txn-id: %Lu", mgmt_msg_code_name(msg->code), adapter->name,
++ txn_id);
+
+ ret = mgmt_msg_native_send_msg(adapter->conn, msg, false);
+ if (ret)
+- _log_err("Could not send CFG_REQ txn-id: %Lu to client '%s", msg->refer_id,
+- adapter->name);
+- return ret;
+-}
+-
+-int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter,
+- uint64_t txn_id)
+-{
+- struct mgmt_msg_cfg_apply_req *msg;
+- int ret;
+-
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_apply_req, 0,
+- MTYPE_MSG_NATIVE_CFG_APPLY_REQ);
+- msg->code = MGMT_MSG_CODE_CFG_APPLY_REQ;
+- msg->refer_id = txn_id;
+-
+- _dbg("Sending CFG_APPLY_REQ to '%s' txn-id: %Lu", adapter->name, txn_id);
+-
+- ret = mgmt_msg_native_send_msg(adapter->conn, msg, false);
+- mgmt_msg_native_free_msg(msg);
++ _log_err("Failed sending %s to '%s' txn-id: %Lu", mgmt_msg_code_name(msg->code),
++ adapter->name, txn_id);
+ return ret;
+ }
+
+-int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg)
+-{
+- struct mgmt_be_client_adapter *adapter = mgmt_be_get_adapter_by_id(id);
+-
+- if (!adapter)
+- return -1;
+-
+- return mgmt_msg_native_send_msg(adapter->conn, msg, false);
+-}
+-
+ /*
+ * Send notification to back-ends that subscribed for them.
+ */
+@@ -599,9 +656,13 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t
+ static void be_adapter_handle_subscribe(struct mgmt_msg_subscribe *msg, size_t msg_len,
+ struct mgmt_be_client_adapter *adapter)
+ {
++ struct mgmt_be_client_adapter *old;
+ const char **s = NULL;
+ uint i = 0;
+
++ _dbg("SUBSCRIBE '%s' to register xpaths config: %u oper: %u notif: %u rpc: %u",
++ adapter->name, msg->nconfig, msg->noper, msg->nnotify, msg->nrpc);
++
+ s = mgmt_msg_native_strings_decode(msg, msg_len, msg->strings);
+ if (!s) {
+ _log_err("Corrupt subscribe msg from '%s'", adapter->name);
+@@ -621,498 +682,336 @@ static void be_adapter_handle_subscribe(struct mgmt_msg_subscribe *msg, size_t m
+ if (adapter->id >= MGMTD_BE_CLIENT_ID_MAX) {
+ _log_err("Unable to resolve adapter '%s' to a valid ID. Disconnecting!",
+ adapter->name);
+- /* this will/should delete old */
+- msg_conn_disconnect(adapter->conn, false);
++ be_adapter_delete(adapter);
+ goto done;
+ }
++
++ old = mgmt_be_adapters_by_id[adapter->id];
++ if (old) {
++ _dbg("be-client: %s using fd: %d reconnected with fd: %d", old->name,
++ old->conn->fd, adapter->conn->fd);
++ be_adapter_delete(old);
++ }
+ mgmt_be_adapters_by_id[adapter->id] = adapter;
+- mgmt_be_adapter_cleanup_old_conn(adapter);
+
+ /* schedule INIT sequence now that it is registered */
+- mgmt_be_adapter_sched_init_event(adapter);
++ be_adapter_sched_init_event(adapter);
+
+ for (uint j = 0; j < msg->nconfig; j++)
+- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
++ be_adapter_register_client_xpath(adapter->id, s[i++],
++ MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
+
+ for (uint j = 0; j < msg->noper; j++)
+- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
++ be_adapter_register_client_xpath(adapter->id, s[i++],
++ MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+
+ for (uint j = 0; j < msg->nnotify; j++)
+- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF);
++ be_adapter_register_client_xpath(adapter->id, s[i++],
++ MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF);
+
+ for (uint j = 0; j < msg->nrpc; j++)
+- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_RPC);
++ be_adapter_register_client_xpath(adapter->id, s[i++],
++ MGMT_BE_XPATH_SUBSCR_TYPE_RPC);
+ done:
+ darr_free_free(s);
+ }
+
+-/*
+- * Handle a native encoded message
+- */
+-static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter,
+- struct mgmt_msg_header *msg,
+- size_t msg_len)
++
++static void be_adapter_process_msg(uint8_t version, uint8_t *data, size_t msg_len,
++ struct msg_conn *conn)
+ {
+- struct mgmt_msg_subscribe *subr_msg;
++ struct mgmt_be_client_adapter *adapter = conn->user;
++ struct mgmt_msg_header *msg = (typeof(msg))data;
+ struct mgmt_msg_notify_data *notify_msg;
+- struct mgmt_msg_txn_reply *txn_msg;
+ struct mgmt_msg_error *error_msg;
+
+- /* get the transaction */
++ if (version != MGMT_MSG_VERSION_NATIVE) {
++ _log_err("Protobuf not supported for backend messages (adapter: %s)",
++ adapter->name);
++ return;
++ }
++ if (msg_len < sizeof(*msg)) {
++ _log_err("native message to adapter %s too short %zu", adapter->name, msg_len);
++ return;
++ }
+
+- switch (msg->code) {
+- case MGMT_MSG_CODE_SUBSCRIBE:
+- subr_msg = (typeof(subr_msg))msg;
+- _dbg("Got SUBSCRIBE from '%s' to register xpaths config: %u oper: %u notif: %u rpc: %u",
+- adapter->name, subr_msg->nconfig, subr_msg->noper, subr_msg->nnotify,
+- subr_msg->nrpc);
++ /*
++ * Most messages are sent to the TXN module for processing.
++ *
++ * NOTE: Handling the config messages may lead to disconnect and
++ * deletion of the adapater. So don't do with it after calling the txn
++ * function.
++ */
+
+- be_adapter_handle_subscribe(subr_msg, msg_len, adapter);
+- break;
++ _dbg("Got %s from '%s' txn-id %Lu", mgmt_msg_code_name(msg->code), adapter->name,
++ msg->refer_id);
+
++ switch (msg->code) {
++ case MGMT_MSG_CODE_SUBSCRIBE:
++ be_adapter_handle_subscribe((struct mgmt_msg_subscribe *)msg, msg_len, adapter);
++ return;
+ case MGMT_MSG_CODE_TXN_REPLY:
+- txn_msg = (typeof(txn_msg))msg;
+- _dbg("Got TXN_REPLY from '%s' txn-id %Lu successfully '%s'", adapter->name,
+- txn_msg->refer_id, txn_msg->created ? "Created" : "Deleted");
+- /*
+- * Forward the TXN_REPLY to txn module.
+- */
+- mgmt_txn_notify_be_txn_reply(txn_msg->refer_id, txn_msg->created, true, adapter);
+- break;
++ assert(0);
++ return;
+ case MGMT_MSG_CODE_CFG_REPLY:
+- _dbg("Got successful CFG_REPLY from '%s' txn-id %Lu", adapter->name, msg->refer_id);
+- /*
+- * Forward the CGFData-create reply to txn module.
+- */
+- mgmt_txn_notify_be_cfg_reply(msg->refer_id, true, NULL, adapter);
+- break;
++ mgmt_txn_handle_cfg_reply(msg->refer_id, adapter);
++ return;
+ case MGMT_MSG_CODE_CFG_APPLY_REPLY:
+- _dbg("Got successful CFG_APPLY_REPLY from '%s' txn-id %Lu", adapter->name,
+- msg->refer_id);
+- /*
+- * Forward the CGFData-apply reply to txn module.
+- */
+- mgmt_txn_notify_be_cfg_apply_reply(msg->refer_id, true, NULL, adapter);
+- break;
+-
++ mgmt_txn_handle_cfg_apply_reply(msg->refer_id, adapter);
++ return;
+ case MGMT_MSG_CODE_ERROR:
+ error_msg = (typeof(error_msg))msg;
+- _dbg("Got ERROR from '%s' txn-id %Lu", adapter->name, msg->refer_id);
+-
+- /* Forward the reply to the txn module */
+- mgmt_txn_notify_error(adapter, msg->refer_id, msg->req_id,
+- error_msg->error, error_msg->errstr);
+- /* We may have lost our connection and adapter at this point */
+- break;
++ mgmt_txn_handle_error_reply(adapter, msg->refer_id, msg->req_id, error_msg->error,
++ error_msg->errstr);
++ return;
+ case MGMT_MSG_CODE_TREE_DATA:
+- /* tree data from a backend client */
+- _dbg("Got TREE_DATA from '%s' txn-id %" PRIu64, adapter->name, msg->refer_id);
+-
+- /* Forward the reply to the txn module */
+- mgmt_txn_notify_tree_data_reply(adapter, (struct mgmt_msg_tree_data *)msg, msg_len);
+- break;
++ mgmt_txn_handle_tree_data_reply(adapter, (struct mgmt_msg_tree_data *)msg, msg_len);
++ return;
+ case MGMT_MSG_CODE_RPC_REPLY:
+- /* RPC reply from a backend client */
+- _dbg("Got RPC_REPLY from '%s' txn-id %" PRIu64, adapter->name, msg->refer_id);
+-
+- /* Forward the reply to the txn module */
+- mgmt_txn_notify_rpc_reply(adapter, (struct mgmt_msg_rpc_reply *)msg, msg_len);
+- break;
++ mgmt_txn_handle_rpc_reply(adapter, (struct mgmt_msg_rpc_reply *)msg, msg_len);
++ return;
+ case MGMT_MSG_CODE_NOTIFY:
+ /*
+- * Handle notify message from a back-end client
++ * Handle notify message from a back-end client no TXN for this.
+ */
+ notify_msg = (typeof(notify_msg))msg;
+- _dbg("Got NOTIFY from '%s'", adapter->name);
+ mgmt_be_adapter_send_notify(notify_msg, msg_len, adapter);
+ mgmt_fe_adapter_send_notify(notify_msg, msg_len);
+- break;
++ return;
+ default:
+ _log_err("unknown native message txn-id %" PRIu64 " req-id %" PRIu64
+ " code %u from BE client for adapter %s",
+ msg->refer_id, msg->req_id, msg->code, adapter->name);
+- break;
+- }
+-}
+-
+-
+-static void mgmt_be_adapter_process_msg(uint8_t version, uint8_t *data,
+- size_t len, struct msg_conn *conn)
+-{
+- struct mgmt_be_client_adapter *adapter = conn->user;
+-
+- if (version == MGMT_MSG_VERSION_NATIVE) {
+- struct mgmt_msg_header *msg = (typeof(msg))data;
+-
+- if (len >= sizeof(*msg))
+- be_adapter_handle_native_msg(adapter, msg, len);
+- else
+- _log_err("native message to adapter %s too short %zu", adapter->name, len);
+ return;
+ }
+-
+- _log_err("Protobuf not supported for backend messages (adapter: %s)", adapter->name);
+ }
+
+-/*
+- * Args for callback
+- */
+-struct mgmt_be_get_adapter_config_params {
+- struct mgmt_be_client_adapter *adapter;
+- struct nb_config_cbs *cfg_chgs;
+- uint32_t seq;
+-};
++/* =================== */
++/* Backend VTY support */
++/* =================== */
+
+-/*
+- * Initialize a BE client over a new connection
+- */
+-static void mgmt_be_adapter_conn_init(struct event *thread)
++void mgmt_be_adapter_status_write(struct vty *vty)
+ {
+ struct mgmt_be_client_adapter *adapter;
++ uint count = 0;
+
+- adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread);
+- assert(adapter && adapter->conn->fd >= 0);
++ vty_out(vty, "MGMTD Backend Adapters\n");
+
+- /*
+- * Notify TXN module to create a CONFIG transaction and
+- * download the CONFIGs identified for this new client.
+- * If the TXN module fails to initiate the CONFIG transaction
+- * retry a bit later. It only fails if there's an existing config
+- * transaction in progress.
+- */
+- if (mgmt_txn_notify_be_adapter_conn(adapter, true) != 0) {
+- zlog_err("XXX txn in progress, retry init");
+- mgmt_be_adapter_sched_init_event(adapter);
+- return;
++ LIST_FOREACH (adapter, &be_adapters, link) {
++ vty_out(vty, " Client: \t\t\t%s\n", adapter->name);
++ vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd);
++ vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id);
++ vty_out(vty, " Msg-Recvd: \t\t\t%Lu\n", adapter->conn->mstate.nrxm);
++ vty_out(vty, " Bytes-Recvd: \t\t%Lu\n", adapter->conn->mstate.nrxb);
++ vty_out(vty, " Msg-Sent: \t\t\t%Lu\n", adapter->conn->mstate.ntxm);
++ vty_out(vty, " Bytes-Sent: \t\t%Lu\n", adapter->conn->mstate.ntxb);
++ count++;
+ }
++ vty_out(vty, " Total: %u\n", count);
+ }
+
+-/*
+- * Schedule the initialization of the BE client connection.
+- */
+-static void
+-mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter)
+-{
+- event_add_timer_msec(mgmt_loop, mgmt_be_adapter_conn_init, adapter,
+- MGMTD_BE_CONN_INIT_DELAY_MSEC,
+- &adapter->conn_init_ev);
+-}
+-
+-void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter)
++static void _show_xpath_map(struct vty *vty, struct mgmt_be_xpath_map *map)
+ {
+- adapter->refcount++;
+-}
++ enum mgmt_be_client_id id;
++ const char *astr;
+
+-extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter)
+-{
+- struct mgmt_be_client_adapter *a = *adapter;
+- assert(a && a->refcount);
+-
+- if (!--a->refcount) {
+- mgmt_be_adapters_del(&mgmt_be_adapters, a);
+- event_cancel(&a->conn_init_ev);
+- msg_server_conn_delete(a->conn);
+- XFREE(MTYPE_MGMTD_BE_ADPATER, a);
++ vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix);
++ FOREACH_BE_CLIENT_BITS (id, map->clients) {
++ astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive";
++ vty_out(vty, " -- %s-client: '%s'\n", astr, be_adapter_id_name(id));
+ }
+-
+- *adapter = NULL;
+ }
+
+-/*
+- * Initialize the BE adapter module
+- */
+-void mgmt_be_adapter_init(struct event_loop *tm)
++void mgmt_be_xpath_register_write(struct vty *vty)
+ {
+- char server_path[MAXPATHLEN];
++ struct mgmt_be_xpath_map *map;
+
+- assert(!mgmt_loop);
+- mgmt_loop = tm;
++ vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n", darr_len(be_oper_xpath_map));
++ darr_foreach_p (be_cfg_xpath_map, map)
++ _show_xpath_map(vty, map);
+
+- mgmt_be_adapters_init(&mgmt_be_adapters);
+- mgmt_be_xpath_map_init();
++ vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n",
++ darr_len(be_oper_xpath_map));
++ darr_foreach_p (be_oper_xpath_map, map)
++ _show_xpath_map(vty, map);
+
+- snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME);
++ vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n",
++ darr_len(be_notif_xpath_map));
++ darr_foreach_p (be_notif_xpath_map, map)
++ _show_xpath_map(vty, map);
+
+- if (msg_server_init(&mgmt_be_server, server_path, tm,
+- mgmt_be_create_adapter, "backend", &mgmt_debug_be)) {
+- zlog_err("cannot initialize backend server");
+- exit(1);
+- }
++ vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", darr_len(be_rpc_xpath_map));
++ darr_foreach_p (be_rpc_xpath_map, map)
++ _show_xpath_map(vty, map);
+ }
+
+ /*
+- * Destroy the BE adapter module
++ * Should replace this with proper YANG module
+ */
+-void mgmt_be_adapter_destroy(void)
++void mgmt_be_adapter_show_xpath_registries(struct vty *vty, const char *xpath)
+ {
++ enum mgmt_be_client_id id;
+ struct mgmt_be_client_adapter *adapter;
++ uint64_t cclients, nclients, oclients, rclients, combined;
+
+- msg_server_cleanup(&mgmt_be_server);
+- FOREACH_ADAPTER_IN_LIST (adapter) {
+- mgmt_be_adapter_delete(adapter);
+- }
+- mgmt_be_xpath_map_cleanup();
+-}
+-
+-/*
+- * The server accepted a new connection
+- */
+-struct msg_conn *mgmt_be_create_adapter(int conn_fd, union sockunion *from)
+-{
+- struct mgmt_be_client_adapter *adapter = NULL;
+-
+- assert(!mgmt_be_find_adapter_by_fd(conn_fd));
+-
+- adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER,
+- sizeof(struct mgmt_be_client_adapter));
+- adapter->id = MGMTD_BE_CLIENT_ID_MAX;
+- snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d",
+- conn_fd);
+-
+- mgmt_be_adapter_lock(adapter);
+- mgmt_be_adapters_add_tail(&mgmt_be_adapters, adapter);
+- RB_INIT(nb_config_cbs, &adapter->cfg_chgs);
+-
+- adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd,
+- mgmt_be_adapter_notify_disconnect,
+- mgmt_be_adapter_process_msg,
+- MGMTD_BE_MAX_NUM_MSG_PROC,
+- MGMTD_BE_MAX_NUM_MSG_WRITE,
+- MGMTD_BE_MAX_MSG_LEN, adapter,
+- "BE-adapter");
+-
+- adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL);
+-
+- _dbg("Added new MGMTD Backend adapter '%s'", adapter->name);
+-
+- return adapter->conn;
+-}
++ cclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
++ oclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
++ nclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF);
++ rclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_RPC);
++ combined = cclients | nclients | oclients | rclients;
+
+-struct mgmt_be_client_adapter *
+-mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id)
+-{
+- return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL);
++ vty_out(vty, "XPath: '%s'\n", xpath);
++ FOREACH_BE_CLIENT_BITS (id, combined) {
++ vty_out(vty, " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n",
++ be_adapter_id_name(id), IS_IDBIT_SET(cclients, id),
++ IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id),
++ IS_IDBIT_SET(rclients, id));
++ adapter = mgmt_be_get_adapter_by_id(id);
++ if (adapter)
++ vty_out(vty, " -- Adapter: %p\n", adapter);
++ }
+ }
+
+-struct mgmt_be_client_adapter *
+-mgmt_be_get_adapter_by_name(const char *name)
+-{
+- return mgmt_be_find_adapter_by_name(name);
+-}
++/* ========================== */
++/* Backend Adapter Management */
++/* ========================== */
+
+ void mgmt_be_adapter_toggle_client_debug(bool set)
+ {
+ struct mgmt_be_client_adapter *adapter;
+
+- FOREACH_ADAPTER_IN_LIST (adapter)
++ LIST_FOREACH (adapter, &be_adapters, link)
+ adapter->conn->debug = set;
+ }
+
+ /*
+- * Get a full set of changes for all the config that an adapter is subscribed to
+- * receive.
++ * Delete a BE client adapter
+ */
+-void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter,
+- struct nb_config_cbs **changes)
++static void be_adapter_delete(struct mgmt_be_client_adapter *adapter)
+ {
+- const struct lyd_node *root, *dnode;
+- uint32_t seq = 0;
+- char *xpath;
+-
+- /* We can't be in the middle of sending other chgs when here. */
+- assert(RB_EMPTY(nb_config_cbs, &adapter->cfg_chgs));
+-
+- *changes = &adapter->cfg_chgs;
+- LY_LIST_FOR (running_config->dnode, root) {
+- LYD_TREE_DFS_BEGIN (root, dnode) {
+- if (lysc_is_key(dnode->schema))
+- goto walk_cont;
++ _dbg("deleting client adapter '%s'", adapter->name);
+
+- xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
+- if (be_is_client_interested(xpath, adapter->id,
+- MGMT_BE_XPATH_SUBSCR_TYPE_CFG))
+- nb_config_diff_add_change(*changes, NB_CB_CREATE, &seq, dnode);
+- else
+- LYD_TREE_DFS_continue = 1; /* skip any subtree */
+- free(xpath);
+- walk_cont:
+- LYD_TREE_DFS_END(root, dnode);
+- }
++ /*
++ * Notify about disconnect for appropriate cleanup
++ */
++ mgmt_txn_handle_be_adapter_connect(adapter, false);
++ if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) {
++ mgmt_be_adapters_by_id[adapter->id] = NULL;
++ adapter->id = MGMTD_BE_CLIENT_ID_MAX;
+ }
++
++ LIST_REMOVE(adapter, link);
++ event_cancel(&adapter->conn_init_ev);
++ msg_server_conn_delete(adapter->conn);
++ XFREE(MTYPE_MGMTD_BE_ADPATER, adapter);
+ }
+
+-uint64_t mgmt_be_interested_clients(const char *xpath,
+- enum mgmt_be_xpath_subscr_type type)
++static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn)
+ {
+- struct mgmt_be_xpath_map *maps = NULL, *map;
+- enum mgmt_be_client_id id;
+- uint64_t clients;
+- bool wild_root;
++ struct mgmt_be_client_adapter *adapter = conn->user;
+
+- switch (type) {
+- case MGMT_BE_XPATH_SUBSCR_TYPE_CFG:
+- maps = be_cfg_xpath_map;
+- break;
+- case MGMT_BE_XPATH_SUBSCR_TYPE_OPER:
+- maps = be_oper_xpath_map;
+- break;
+- case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF:
+- maps = be_notif_xpath_map;
+- break;
+- case MGMT_BE_XPATH_SUBSCR_TYPE_RPC:
+- maps = be_rpc_xpath_map;
+- break;
+- }
++ _dbg("notify disconnect for client adapter '%s'", adapter->name);
+
+- clients = 0;
++ be_adapter_delete(adapter);
+
+- _dbg("XPATH: '%s'", xpath);
++ return 0;
++}
+
+- /* wild_root will select all clients that advertise op-state */
+- wild_root = !strcmp(xpath, "/") || !strcmp(xpath, "/*");
+- darr_foreach_p (maps, map)
+- if (wild_root || mgmt_be_xpath_prefix(map->xpath_prefix, xpath))
+- clients |= map->clients;
++/*
++ * Initialize a BE client over a new connection
++ */
++static void be_adapter_conn_init(struct event *thread)
++{
++ struct mgmt_be_client_adapter *adapter;
++ enum mgmt_be_client_id id;
+
+- if (DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL)) {
+- FOREACH_BE_CLIENT_BITS (id, clients)
+- _dbg("Cient: %s: subscribed", mgmt_be_client_id2name(id));
++ adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread);
++ assert(adapter && adapter->conn->fd >= 0);
++ id = adapter->id;
++
++ /*
++ * Notify TXN module to create a CONFIG transaction and
++ * download the CONFIGs identified for this new client.
++ * If the TXN module fails to initiate the CONFIG transaction
++ * retry a bit later. It only fails if there's an existing config
++ * transaction in progress.
++ */
++ if (mgmt_txn_handle_be_adapter_connect(adapter, true) != 0) {
++ /* Deal with a disconnect happening */
++ if (!mgmt_be_adapters_by_id[id])
++ return;
++ _log_err("Couldn't send intial config to adapter: %s", adapter->name);
++ be_adapter_sched_init_event(adapter);
+ }
+- return clients;
+ }
+
+-bool mgmt_is_mgmtd_interested(const char *xpath)
++/*
++ * Schedule the initialization of the BE client connection.
++ */
++static void be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter)
+ {
+- const char *const *match = mgmtd_config_xpaths;
+- const char *const *ematch = match + array_size(mgmtd_config_xpaths);
+-
+- for (; match < ematch; match++) {
+- if (mgmt_be_xpath_prefix(*match, xpath)) {
+- _dbg("mgmtd: subscribed to %s", xpath);
+- return true;
+- }
+- }
+- return false;
++ event_add_timer_msec(mgmt_loop, be_adapter_conn_init, adapter,
++ MGMTD_BE_CONN_INIT_DELAY_MSEC, &adapter->conn_init_ev);
+ }
+
+-/**
+- * Return true if `client_id` is interested in `xpath` for `config`
+- * or oper (!`config`).
+- *
+- * Args:
+- * xpath - the xpath to check for interest.
+- * client_id - the BE client being checked for.
+- * bool - check for config (vs oper) subscription.
+- *
+- * Returns:
+- * Interested or not.
++/*
++ * The server accepted a new connection
+ */
+-static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id,
+- enum mgmt_be_xpath_subscr_type type)
++static struct msg_conn *be_adapter_create(int conn_fd, union sockunion *from)
+ {
+- uint64_t clients;
++ struct mgmt_be_client_adapter *adapter = NULL;
+
+- assert(id < MGMTD_BE_CLIENT_ID_MAX);
++ assert(!mgmt_be_find_adapter_by_fd(conn_fd));
+
+- _dbg("Checking client: %s for xpath: '%s'", mgmt_be_client_id2name(id), xpath);
++ adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER, sizeof(struct mgmt_be_client_adapter));
++ adapter->id = MGMTD_BE_CLIENT_ID_MAX;
++ snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd);
+
+- clients = mgmt_be_interested_clients(xpath, type);
+- if (IS_IDBIT_SET(clients, id)) {
+- _dbg("client: %s: interested", mgmt_be_client_id2name(id));
+- return true;
+- }
++ LIST_INSERT_HEAD(&be_adapters, adapter, link);
+
+- _dbg("client: %s: not interested", mgmt_be_client_id2name(id));
+- return false;
+-}
++ adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd,
++ mgmt_be_adapter_notify_disconnect,
++ be_adapter_process_msg, MGMTD_BE_MAX_NUM_MSG_PROC,
++ MGMTD_BE_MAX_NUM_MSG_WRITE, MGMTD_BE_MAX_MSG_LEN,
++ adapter, "BE-adapter");
+
+-void mgmt_be_adapter_status_write(struct vty *vty)
+-{
+- struct mgmt_be_client_adapter *adapter;
++ adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL);
+
+- vty_out(vty, "MGMTD Backend Adapters\n");
++ _dbg("Added new MGMTD Backend adapter '%s'", adapter->name);
+
+- FOREACH_ADAPTER_IN_LIST (adapter) {
+- vty_out(vty, " Client: \t\t\t%s\n", adapter->name);
+- vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd);
+- vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id);
+- vty_out(vty, " Ref-Count: \t\t\t%u\n", adapter->refcount);
+- vty_out(vty, " Msg-Recvd: \t\t\t%" PRIu64 "\n",
+- adapter->conn->mstate.nrxm);
+- vty_out(vty, " Bytes-Recvd: \t\t%" PRIu64 "\n",
+- adapter->conn->mstate.nrxb);
+- vty_out(vty, " Msg-Sent: \t\t\t%" PRIu64 "\n",
+- adapter->conn->mstate.ntxm);
+- vty_out(vty, " Bytes-Sent: \t\t%" PRIu64 "\n",
+- adapter->conn->mstate.ntxb);
+- }
+- vty_out(vty, " Total: %d\n",
+- (int)mgmt_be_adapters_count(&mgmt_be_adapters));
++ return adapter->conn;
+ }
+
+-static void be_show_xpath_register(struct vty *vty,
+- struct mgmt_be_xpath_map *map)
+-{
+- enum mgmt_be_client_id id;
+- const char *astr;
+-
+- vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix);
+- FOREACH_BE_CLIENT_BITS (id, map->clients) {
+- astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive";
+- vty_out(vty, " -- %s-client: '%s'\n", astr,
+- mgmt_be_client_id2name(id));
+- }
+-}
+-void mgmt_be_xpath_register_write(struct vty *vty)
++/*
++ * Initialize the BE adapter module
++ */
++void mgmt_be_adapter_init(struct event_loop *tm)
+ {
+- struct mgmt_be_xpath_map *map;
++ char server_path[MAXPATHLEN];
+
+- vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n",
+- darr_len(be_oper_xpath_map));
+- darr_foreach_p (be_cfg_xpath_map, map)
+- be_show_xpath_register(vty, map);
++ assert(!mgmt_loop);
++ mgmt_loop = tm;
+
+- vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n",
+- darr_len(be_oper_xpath_map));
+- darr_foreach_p (be_oper_xpath_map, map)
+- be_show_xpath_register(vty, map);
++ be_adapter_xpath_maps_init();
+
+- vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n",
+- darr_len(be_notif_xpath_map));
+- darr_foreach_p (be_notif_xpath_map, map)
+- be_show_xpath_register(vty, map);
++ snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME);
+
+- vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n",
+- darr_len(be_rpc_xpath_map));
+- darr_foreach_p (be_rpc_xpath_map, map)
+- be_show_xpath_register(vty, map);
++ if (msg_server_init(&mgmt_be_server, server_path, tm, be_adapter_create, "backend",
++ &mgmt_debug_be)) {
++ zlog_err("cannot initialize backend server");
++ exit(1);
++ }
+ }
+
+-void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath)
++/*
++ * Destroy the BE adapter module
++ */
++void mgmt_be_adapter_destroy(void)
+ {
+- enum mgmt_be_client_id id;
+- struct mgmt_be_client_adapter *adapter;
+- uint64_t cclients, nclients, oclients, rclients, combined;
+-
+- cclients = mgmt_be_interested_clients(xpath,
+- MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
+- oclients = mgmt_be_interested_clients(xpath,
+- MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+- nclients = mgmt_be_interested_clients(xpath,
+- MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF);
+- rclients = mgmt_be_interested_clients(xpath,
+- MGMT_BE_XPATH_SUBSCR_TYPE_RPC);
+- combined = cclients | nclients | oclients | rclients;
++ struct mgmt_be_client_adapter *adapter, *next;
+
+- vty_out(vty, "XPath: '%s'\n", xpath);
+- FOREACH_BE_CLIENT_BITS (id, combined) {
+- vty_out(vty,
+- " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n",
+- mgmt_be_client_id2name(id), IS_IDBIT_SET(cclients, id),
+- IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id),
+- IS_IDBIT_SET(rclients, id));
+- adapter = mgmt_be_get_adapter_by_id(id);
+- if (adapter)
+- vty_out(vty, " -- Adapter: %p\n", adapter);
+- }
++ msg_server_cleanup(&mgmt_be_server);
++ LIST_FOREACH_SAFE (adapter, &be_adapters, link, next)
++ be_adapter_delete(adapter);
++ be_adapter_xpath_maps_cleanup();
+ }
+diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h
+index 26dd56ac62..390ae078e9 100644
+--- a/mgmtd/mgmt_be_adapter.h
++++ b/mgmtd/mgmt_be_adapter.h
+@@ -14,6 +14,7 @@
+ #include "mgmt_msg.h"
+ #include "mgmt_defines.h"
+ #include "mgmtd/mgmt_ds.h"
++#include "queue.h"
+
+ #define MGMTD_BE_CONN_INIT_DELAY_MSEC 50
+
+@@ -45,7 +46,6 @@ enum mgmt_be_client_id {
+
+
+ PREDECL_LIST(mgmt_be_adapters);
+-PREDECL_LIST(mgmt_txn_badapters);
+
+ struct mgmt_be_client_adapter {
+ struct msg_conn *conn;
+@@ -53,38 +53,13 @@ struct mgmt_be_client_adapter {
+ struct event *conn_init_ev;
+
+ enum mgmt_be_client_id id;
+- uint32_t flags;
+ char name[MGMTD_CLIENT_NAME_MAX_LEN];
+
+- int refcount;
+-
+- /*
+- * List of config items that should be sent to the
+- * backend during re/connect. This is temporarily
+- * created and then freed-up as soon as the initial
+- * config items has been applied onto the backend.
+- */
+- struct nb_config_cbs cfg_chgs;
+ struct mgmt_commit_stats cfg_stats;
+-
+ struct mgmt_be_adapters_item list_linkage;
+-};
+-
+-#define MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED (1U << 0)
+
+-DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage);
+-
+-/*
+- * MGMT_SUBSCR_xxx - flags for subscription types for xpaths registrations
+- *
+- * MGMT_SUBSCR_VALIDATE_CFG :: the client should be asked to validate config
+- * MGMT_SUBSCR_NOTIFY_CFG :: the client should be notified of config changes
+- * MGMT_SUBSCR_OPER_OWN :: the client owns the given oeprational state
+- */
+-#define MGMT_SUBSCR_VALIDATE_CFG 0x1
+-#define MGMT_SUBSCR_NOTIFY_CFG 0x2
+-#define MGMT_SUBSCR_OPER_OWN 0x4
+-#define MGMT_SUBSCR_ALL 0x7
++ LIST_ENTRY(mgmt_be_client_adapter) link;
++};
+
+ /* --------- */
+ /* CLIENT-ID */
+@@ -94,8 +69,11 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage);
+ for ((id) = MGMTD_BE_CLIENT_ID_MIN; (id) < MGMTD_BE_CLIENT_ID_MAX; \
+ (id)++)
+
++#define IDBIT_MASK(id) (1ull << (id))
++#define IS_IDBIT_UNSET(v, id) (!((v)&IDBIT_MASK(id)))
+ #define IS_IDBIT_SET(v, id) (!IS_IDBIT_UNSET(v, id))
+-#define IS_IDBIT_UNSET(v, id) (!((v) & (1ull << (id))))
++#define SET_IDBIT(v, id) ((v) |= IDBIT_MASK(id))
++#define UNSET_IDBIT(v, id) ((v) &= ~IDBIT_MASK(id))
+
+ #define _GET_NEXT_SET(id, bits) \
+ ({ \
+@@ -111,6 +89,26 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage);
+ for ((id) = _GET_NEXT_SET(MGMTD_BE_CLIENT_ID_MIN, bits); (id) < MGMTD_BE_CLIENT_ID_MAX; \
+ (id) = _GET_NEXT_SET((id) + 1, bits))
+
++/* This is required to avoid assignment in if conditional in FOREACH_BE_ADAPTER_BITS :( */
++#define _GET_NEXT_SET_ADAPTER(id, adapter, bits) \
++ ({ \
++ enum mgmt_be_client_id _gnsa_id = (id); \
++ \
++ for (; _gnsa_id < MGMTD_BE_CLIENT_ID_MAX; _gnsa_id++) { \
++ if (IS_IDBIT_SET(bits, _gnsa_id)) { \
++ (adapter) = mgmt_be_get_adapter_by_id(_gnsa_id); \
++ if ((adapter)) \
++ break; \
++ } \
++ } \
++ _gnsa_id; \
++ })
++
++#define FOREACH_BE_ADAPTER_BITS(id, adapter, bits) \
++ for ((id) = _GET_NEXT_SET_ADAPTER(MGMTD_BE_CLIENT_ID_MIN, (adapter), (bits)); \
++ (id) < MGMTD_BE_CLIENT_ID_MAX; \
++ (id) = _GET_NEXT_SET_ADAPTER((id) + 1, (adapter), (bits)))
++
+ /* ---------- */
+ /* Prototypes */
+ /* ---------- */
+@@ -121,20 +119,6 @@ extern void mgmt_be_adapter_init(struct event_loop *tm);
+ /* Destroy the backend adapter module. */
+ extern void mgmt_be_adapter_destroy(void);
+
+-/* Acquire lock for backend adapter. */
+-extern void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter);
+-
+-/* Remove lock from backend adapter. */
+-extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter);
+-
+-/* Create backend adapter. */
+-extern struct msg_conn *mgmt_be_create_adapter(int conn_fd,
+- union sockunion *su);
+-
+-/* Fetch backend adapter given an adapter name. */
+-extern struct mgmt_be_client_adapter *
+-mgmt_be_get_adapter_by_name(const char *name);
+-
+ /* Fetch backend adapter given an client ID. */
+ extern struct mgmt_be_client_adapter *
+ mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id);
+@@ -146,42 +130,7 @@ extern const char *mgmt_be_client_id2name(enum mgmt_be_client_id id);
+ extern void mgmt_be_adapter_toggle_client_debug(bool set);
+
+ /* Fetch backend adapter config. */
+-extern void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter,
+- struct nb_config_cbs **changes);
+-
+-/* Create/destroy a transaction. */
+-extern int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter,
+- uint64_t txn_id, bool create);
+-
+-/*
+- * Send config data create request to backend client.
+- *
+- * adaptr
+- * Backend adapter information.
+- *
+- * msg
+- * The already constructed message to send.
+- *
+- * Returns:
+- * 0 on success, -1 on failure.
+- */
+-extern int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter,
+- struct mgmt_msg_cfg_req *msg);
+-
+-/*
+- * Send config apply request to backend client.
+- *
+- * adapter
+- * Backend adapter information.
+- *
+- * txn_id
+- * Unique transaction identifier.
+- *
+- * Returns:
+- * 0 on success, -1 on failure.
+- */
+-extern int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter,
+- uint64_t txn_id);
++extern struct nb_config_cbs mgmt_be_adapter_get_config(struct mgmt_be_client_adapter *adapter);
+
+ /*
+ * Dump backend adapter status to vty.
+@@ -204,7 +153,7 @@ extern void mgmt_be_xpath_register_write(struct vty *vty);
+ * Return:
+ * Any return value from msg_conn_send_msg().
+ */
+-extern int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg);
++extern int mgmt_be_adapter_send(struct mgmt_be_client_adapter *adapter, void *msg);
+
+ enum mgmt_be_xpath_subscr_type {
+ MGMT_BE_XPATH_SUBSCR_TYPE_CFG,
+@@ -224,17 +173,10 @@ enum mgmt_be_xpath_subscr_type {
+ extern uint64_t mgmt_be_interested_clients(const char *xpath,
+ enum mgmt_be_xpath_subscr_type type);
+
+-/**
+- * mgmt_fe_adapter_send_notify() - notify FE clients of a notification.
+- * @msg: the notify message from the backend client.
+- * @msglen: the length of the notify message.
+- */
+-extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg,
+- size_t msglen);
+ /*
+ * Dump backend client information for a given xpath to vty.
+ */
+-extern void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath);
++extern void mgmt_be_adapter_show_xpath_registries(struct vty *vty, const char *xpath);
+
+ /*
+ * Specials for mgmtd internally handling BE like behaviors
+diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c
+index a0e6c83393..4dae8f5222 100644
+--- a/mgmtd/mgmt_ds.c
++++ b/mgmtd/mgmt_ds.c
+@@ -16,12 +16,14 @@
+ #include "libyang/libyang.h"
+
+ #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_ds, "DS: %s: " fmt, __func__, ##__VA_ARGS__)
++#define _log_warn(fmt, ...) zlog_warn("%s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+ #define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+
+ struct mgmt_ds_ctx {
+ enum mgmt_ds_id ds_id;
+
+ bool locked;
++ uint64_t txn_locked; /* lock held by txn */
+ uint64_t vty_session_id; /* Owner of the lock or 0 */
+
+ bool config_ds;
+@@ -222,14 +224,17 @@ bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx)
+ return ds_ctx->config_ds;
+ }
+
+-bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id)
++bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id, uint64_t *txn_id)
+ {
+- if (!ds_ctx || !ds_ctx->locked)
++ if (!ds_ctx)
+ return false;
+
+- if (session_id)
++ if (ds_ctx->locked && session_id)
+ *session_id = ds_ctx->vty_session_id;
+- return true;
++ if (ds_ctx->txn_locked && txn_id)
++ *txn_id = ds_ctx->txn_locked;
++
++ return ds_ctx->locked || ds_ctx->txn_locked;
+ }
+
+ int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id)
+@@ -237,31 +242,93 @@ int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id)
+ assert(ds_ctx);
+
+ if (ds_ctx->locked) {
+- _log_err("lock already taken on DS:%s by session-id %Lu",
++ _log_err("LOCK already taken on DS:%s by session-id %Lu",
+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id);
+- return EBUSY;
++ return -EBUSY;
+ }
+-
+- _dbg("obtaining lock on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id),
+- session_id);
++ if (ds_ctx->txn_locked) {
++ _log_err("LOCK already taken on DS:%s by session-less txn-id %Lu",
++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked);
++ return -EBUSY;
++ }
++ _dbg("LOCK on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), session_id);
+
+ ds_ctx->locked = true;
+ ds_ctx->vty_session_id = session_id;
+ return 0;
+ }
+
+-void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx)
++void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id)
+ {
+ assert(ds_ctx);
+ if (!ds_ctx->locked)
+ _log_err("unlock on unlocked in DS:%s last session-id %Lu",
+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id);
+- else
++ else {
++ assert(ds_ctx->vty_session_id == session_id);
+ _dbg("releasing lock on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id),
+ ds_ctx->vty_session_id);
++ }
+
+ ds_ctx->locked = 0;
+ ds_ctx->vty_session_id = MGMTD_SESSION_ID_NONE;
++
++ if (ds_ctx->txn_locked)
++ _dbg("TXN-LOCK remains on DS:%s for txn-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id),
++ ds_ctx->txn_locked);
++}
++
++bool mgmt_ds_is_txn_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *txn_id)
++{
++ if (ds_ctx && ds_ctx->txn_locked) {
++ *txn_id = ds_ctx->txn_locked;
++ return true;
++ }
++ return false;
++}
++
++uint64_t mgmt_ds_txn_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id)
++{
++ assert(ds_ctx);
++
++ if (ds_ctx->txn_locked && ds_ctx->txn_locked != txn_id) {
++ _log_err("TXN-LOCK txn lock held on DS:%s by txn-id: %Lu not txn-id: %Lu",
++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked, txn_id);
++ return ds_ctx->txn_locked;
++ }
++ if (ds_ctx->txn_locked == txn_id) {
++ _log_warn("TXN-LOCK double lock on DS:%s txn-id: %Lu",
++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked);
++ return 0;
++ }
++ _dbg("TXN-LOCK on DS:%s session-id: %Lu txn-id: %Lu", mgmt_ds_id2name(ds_ctx->ds_id),
++ ds_ctx->vty_session_id, txn_id);
++ ds_ctx->txn_locked = txn_id;
++ return 0;
++}
++
++void mgmt_ds_txn_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id)
++{
++ assert(ds_ctx);
++ if (ds_ctx->txn_locked && ds_ctx->txn_locked != txn_id) {
++ _log_err("TXN-UNLOCK: txn lock held on DS:%s by session-id: %Lu txn-id: %Lu not txn-id: %Lu",
++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id,
++ ds_ctx->txn_locked, txn_id);
++ return;
++ }
++ if (!ds_ctx->txn_locked)
++ _log_warn("TXN-UNLOCK of unlocked on DS:%s session-id: %Lu txn-id: %Lu",
++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, txn_id);
++ else
++ _dbg("TXN-UNLOCK on DS:%s session-id: %Lu txn-id: %Lu",
++ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, txn_id);
++ ds_ctx->txn_locked = 0;
++}
++
++void mgmt_ds_restore_nb_config(struct mgmt_ds_ctx *ds_ctx, struct nb_config *backup)
++{
++ assert(ds_ctx->config_ds);
++ nb_config_replace(ds_ctx->root.cfg_root, backup, false);
+ }
+
+ int mgmt_ds_copy_dss(struct mgmt_ds_ctx *dst, struct mgmt_ds_ctx *src, bool updt_cmt_rec)
+@@ -514,7 +581,8 @@ void mgmt_ds_dump_tree(struct vty *vty, struct mgmt_ds_ctx *ds_ctx,
+
+ void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx)
+ {
+- uint64_t session_id;
++ uint64_t session_id = 0;
++ uint64_t txn_id = 0;
+ bool locked;
+
+ if (!ds_ctx) {
+@@ -522,13 +590,13 @@ void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx)
+ return;
+ }
+
+- locked = mgmt_ds_is_locked(ds_ctx, &session_id);
++ locked = mgmt_ds_is_locked(ds_ctx, &session_id, &txn_id);
+ vty_out(vty, " DS: %s\n", mgmt_ds_id2name(ds_ctx->ds_id));
+ vty_out(vty, " DS-Hndl: \t\t\t%p\n", ds_ctx);
+ vty_out(vty, " Config: \t\t\t%s\n",
+ ds_ctx->config_ds ? "True" : "False");
+- vty_out(vty, " Locked: \t\t\t%s Session-ID: %Lu\n", locked ? "True" : "False",
+- locked ? session_id : 0);
++ vty_out(vty, " Locked: \t\t\t%s Session-ID: %Lu Txn-ID: %Lu\n",
++ locked ? "True" : "False", session_id, txn_id);
+ }
+
+ void mgmt_ds_status_write(struct vty *vty)
+diff --git a/mgmtd/mgmt_ds.h b/mgmtd/mgmt_ds.h
+index 0849674acd..a509426ef1 100644
+--- a/mgmtd/mgmt_ds.h
++++ b/mgmtd/mgmt_ds.h
+@@ -34,6 +34,7 @@
+
+ extern struct nb_config *running_config;
+
++struct mgmt_master;
+ struct mgmt_ds_ctx;
+
+ /***************************************************************
+@@ -178,10 +179,10 @@ extern struct mgmt_ds_ctx *mgmt_ds_get_ctx_by_id(struct mgmt_master *mm, enum mg
+ extern bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx);
+
+ /*
+- * Check if a given datastore is locked by a session, if so return that session
+- * ID if session_id is not NULL.
++ * Check if a given datastore is locked. If so return that session
++ * ID if session_id is not NULL. Also return txn_id if locked by a config txn.
+ */
+-extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id);
++extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id, uint64_t *txn_id);
+
+ /*
+ * Acquire write lock to a ds given a ds_handle
+@@ -190,8 +191,11 @@ extern int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id);
+
+ /*
+ * Remove a lock from ds given a ds_handle
++ *
++ * session_id
++ * Session ID that holds the lock.
+ */
+-extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx);
++extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id);
+
+ /*
+ * Copy from source to destination datastore.
+@@ -215,6 +219,11 @@ extern int mgmt_ds_copy_dss(struct mgmt_ds_ctx *dst, struct mgmt_ds_ctx *src, bo
+ */
+ extern struct nb_config *mgmt_ds_get_nb_config(struct mgmt_ds_ctx *ds_ctx);
+
++/*
++ * Restore configuration for a given datastore context from a backup.
++ */
++extern void mgmt_ds_restore_nb_config(struct mgmt_ds_ctx *ds_ctx, struct nb_config *backup);
++
+ /*
+ * Find YANG data node given a datastore handle YANG xpath.
+ */
+@@ -329,4 +338,13 @@ extern void mgmt_ds_status_write(struct vty *vty);
+ */
+ void mgmt_ds_reset_candidate(void);
+
++
++/*
++ * Private TXN-Lock functions.
++ */
++
++extern bool mgmt_ds_is_txn_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *txn_id);
++extern uint64_t mgmt_ds_txn_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id);
++extern void mgmt_ds_txn_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id);
++
+ #endif /* _FRR_MGMTD_DS_H_ */
+diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
+index 36f37eb1f0..fcc8c61fac 100644
+--- a/mgmtd/mgmt_fe_adapter.c
++++ b/mgmtd/mgmt_fe_adapter.c
+@@ -26,10 +26,6 @@
+ #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_fe, "FE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__)
+ #define _log_err(fmt, ...) zlog_err("FE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+
+-#define FOREACH_ADAPTER_IN_LIST(adapter) \
+- frr_each_safe (mgmt_fe_adapters, &mgmt_fe_adapters, (adapter))
+-
+-
+ enum mgmt_session_event {
+ MGMTD_FE_SESSION_CFG_TXN_CLNUP = 1,
+ MGMTD_FE_SESSION_SHOW_TXN_CLNUP,
+@@ -47,13 +43,21 @@ struct mgmt_fe_session_ctx {
+ struct event *proc_cfg_txn_clnp;
+ struct event *proc_show_txn_clnp;
+
+- struct mgmt_fe_sessions_item list_linkage;
++ LIST_ENTRY(mgmt_fe_session_ctx) link;
+ };
+
+-DECLARE_LIST(mgmt_fe_sessions, struct mgmt_fe_session_ctx, list_linkage);
++struct mgmt_fe_client_adapter {
++ struct msg_conn *conn;
++ char name[MGMTD_CLIENT_NAME_MAX_LEN];
+
+-#define FOREACH_SESSION_IN_LIST(adapter, session) \
+- frr_each_safe (mgmt_fe_sessions, &(adapter)->fe_sessions, (session))
++ LIST_ENTRY(mgmt_fe_client_adapter) link;
++
++ /* List of sessions created and being maintained for this client. */
++ LIST_HEAD(fe_session_list_head, mgmt_fe_session_ctx) sessions;
++
++ /* NOTE: shared by all sessions, only works b/c one session configuring at a time */
++ struct mgmt_commit_stats cmt_stats;
++};
+
+ /*
+ * A tree for storing unique notify-select strings.
+@@ -67,6 +71,13 @@ struct ns_string {
+ static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2);
+ DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare);
+
++/* ---------- */
++/* Prototypes */
++/* ---------- */
++
++static struct msg_conn *fe_adapter_create(int conn_fd, union sockunion *from);
++static void fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats);
++
+ /* ---------------- */
+ /* Global variables */
+ /* ---------------- */
+@@ -74,7 +85,7 @@ DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare);
+ static struct event_loop *mgmt_loop;
+ static struct msg_server mgmt_fe_server = {.fd = -1};
+
+-static struct mgmt_fe_adapters_head mgmt_fe_adapters;
++LIST_HEAD(fe_adapter_list_head, mgmt_fe_client_adapter) fe_adapters;
+
+ static struct hash *mgmt_fe_sessions;
+ static uint64_t mgmt_fe_next_session_id;
+@@ -164,11 +175,36 @@ char **mgmt_fe_get_all_selectors(void)
+ return selectors;
+ }
+
++enum mgmt_result nb_error_to_mgmt_result(enum nb_error error)
++{
++ switch (error) {
++ case NB_OK:
++ return MGMTD_SUCCESS;
++ case NB_ERR:
++ return MGMTD_INTERNAL_ERROR;
++ case NB_ERR_NO_CHANGES:
++ return MGMTD_NO_CFG_CHANGES;
++ case NB_ERR_NOT_FOUND:
++ return MGMTD_INVALID_PARAM;
++ case NB_ERR_EXISTS:
++ return MGMTD_INVALID_PARAM;
++ case NB_ERR_LOCKED:
++ return MGMTD_DS_LOCK_FAILED;
++ case NB_ERR_VALIDATION:
++ return MGMTD_VALIDATION_ERROR;
++ case NB_ERR_RESOURCE:
++ return MGMTD_INTERNAL_ERROR;
++ case NB_ERR_INCONSISTENCY:
++ return MGMTD_INTERNAL_ERROR;
++ case NB_YIELD:
++ return MGMTD_UNKNOWN_FAILURE;
++ }
++ return MGMTD_UNKNOWN_FAILURE;
++}
+
+-/* Forward declarations */
+-static void
+-mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session,
+- enum mgmt_session_event event);
++/* =========================== */
++/* Frontend Session Management */
++/* =========================== */
+
+ static int mgmt_fe_session_write_lock_ds(enum mgmt_ds_id ds_id, struct mgmt_ds_ctx *ds_ctx,
+ struct mgmt_fe_session_ctx *session)
+@@ -201,80 +237,17 @@ static void mgmt_fe_session_unlock_ds(enum mgmt_ds_id ds_id, struct mgmt_ds_ctx
+ session->session_id, mgmt_ds_id2name(ds_id));
+
+ session->ds_locked[ds_id] = false;
+- mgmt_ds_unlock(ds_ctx);
++ mgmt_ds_unlock(ds_ctx, session->session_id);
+ _dbg("Unlocked DS:%s write-locked earlier by session-id: %" PRIu64 " from %s",
+ mgmt_ds_id2name(ds_id), session->session_id, session->adapter->name);
+ }
+
+-static void
+-mgmt_fe_session_cfg_txn_cleanup(struct mgmt_fe_session_ctx *session)
+-{
+- /*
+- * Destroy the actual transaction created earlier.
+- */
+- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE)
+- mgmt_destroy_txn(&session->cfg_txn_id);
+-}
+-
+-static void
+-mgmt_fe_session_show_txn_cleanup(struct mgmt_fe_session_ctx *session)
+-{
+- /*
+- * Destroy the transaction created recently.
+- */
+- if (session->txn_id != MGMTD_TXN_ID_NONE)
+- mgmt_destroy_txn(&session->txn_id);
+-}
+-
+-static void
+-mgmt_fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats)
+-{
+- cmt_stats->last_exec_tm =
+- timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start);
+- if (cmt_stats->last_exec_tm > cmt_stats->max_tm) {
+- cmt_stats->max_tm = cmt_stats->last_exec_tm;
+- cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt;
+- }
+-
+- if (cmt_stats->last_exec_tm < cmt_stats->min_tm) {
+- cmt_stats->min_tm = cmt_stats->last_exec_tm;
+- cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt;
+- }
+-}
+-
+-static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp)
+-{
+- enum mgmt_ds_id ds_id;
+- struct mgmt_ds_ctx *ds_ctx;
+- struct mgmt_fe_session_ctx *session = *sessionp;
+-
+- if (session->adapter) {
+- mgmt_fe_session_cfg_txn_cleanup(session);
+- mgmt_fe_session_show_txn_cleanup(session);
+- for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) {
+- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id);
+- if (ds_ctx && session->ds_locked[ds_id])
+- mgmt_fe_session_unlock_ds(ds_id, ds_ctx,
+- session);
+- }
+- mgmt_fe_sessions_del(&session->adapter->fe_sessions, session);
+- assert(session->adapter->refcount > 1);
+- mgmt_fe_adapter_unlock(&session->adapter);
+- }
+- mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
+- darr_free_free(session->notify_xpaths);
+- hash_release(mgmt_fe_sessions, session);
+- XFREE(MTYPE_MGMTD_FE_SESSION, session);
+- *sessionp = NULL;
+-}
+-
+ static struct mgmt_fe_session_ctx *
+-mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter,
+- uint64_t client_id)
++fe_session_lookup_by_client_id(struct mgmt_fe_client_adapter *adapter, uint64_t client_id)
+ {
+ struct mgmt_fe_session_ctx *session;
+
+- FOREACH_SESSION_IN_LIST (adapter, session) {
++ LIST_FOREACH (session, &adapter->sessions, link) {
+ if (session->client_id == client_id) {
+ _dbg("Found session-id %" PRIu64 " using client-id %" PRIu64,
+ session->session_id, client_id);
+@@ -285,7 +258,7 @@ mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter,
+ return NULL;
+ }
+
+-static unsigned int mgmt_fe_session_hash_key(const void *data)
++static unsigned int fe_session_hash_key(const void *data)
+ {
+ const struct mgmt_fe_session_ctx *session = data;
+
+@@ -293,7 +266,7 @@ static unsigned int mgmt_fe_session_hash_key(const void *data)
+ sizeof(session->session_id) / sizeof(uint32_t), 0);
+ }
+
+-static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2)
++static bool fe_session_hash_cmp(const void *d1, const void *d2)
+ {
+ const struct mgmt_fe_session_ctx *session1 = d1;
+ const struct mgmt_fe_session_ctx *session2 = d2;
+@@ -301,12 +274,14 @@ static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2)
+ return (session1->session_id == session2->session_id);
+ }
+
+-static inline struct mgmt_fe_session_ctx *
+-mgmt_session_id2ctx(uint64_t session_id)
++static inline struct mgmt_fe_session_ctx *fe_session_lookup(uint64_t session_id)
+ {
+ struct mgmt_fe_session_ctx key = {0};
+ struct mgmt_fe_session_ctx *session;
+
++ if (session_id == MGMTD_SESSION_ID_NONE)
++ return NULL;
++
+ if (!mgmt_fe_sessions)
+ return NULL;
+
+@@ -316,31 +291,47 @@ mgmt_session_id2ctx(uint64_t session_id)
+ return session;
+ }
+
+-void mgmt_fe_adapter_toggle_client_debug(bool set)
++static struct mgmt_fe_session_ctx *fe_session_by_txn_id(uint64_t txn_id)
+ {
+- struct mgmt_fe_client_adapter *adapter;
++ uint64_t session_id = mgmt_txn_get_session_id(txn_id);
+
+- FOREACH_ADAPTER_IN_LIST (adapter)
+- adapter->conn->debug = set;
++ if (session_id == MGMTD_SESSION_ID_NONE)
++ return NULL;
++ return fe_session_lookup(session_id);
+ }
+
+-static struct mgmt_fe_session_ctx *fe_adapter_session_by_txn_id(uint64_t txn_id)
++static void fe_session_cleanup(struct mgmt_fe_session_ctx **sessionp)
+ {
+- uint64_t session_id = mgmt_txn_get_session_id(txn_id);
++ enum mgmt_ds_id ds_id;
++ struct mgmt_ds_ctx *ds_ctx;
++ struct mgmt_fe_session_ctx *session = *sessionp;
+
+- if (session_id == MGMTD_SESSION_ID_NONE)
+- return NULL;
+- return mgmt_session_id2ctx(session_id);
++ /* XXXchopps what about RPC txns? */
++ mgmt_destroy_txn(&session->cfg_txn_id);
++ mgmt_destroy_txn(&session->txn_id);
++ for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) {
++ ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id);
++ if (ds_ctx && session->ds_locked[ds_id])
++ mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
++ }
++
++ LIST_REMOVE(session, link);
++
++ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
++ darr_free_free(session->notify_xpaths);
++ hash_release(mgmt_fe_sessions, session);
++ XFREE(MTYPE_MGMTD_FE_SESSION, session);
++ *sessionp = NULL;
+ }
+
+-static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_adapter *adapter,
+- uint8_t notify_format, uint64_t client_id)
++static struct mgmt_fe_session_ctx *fe_session_create(struct mgmt_fe_client_adapter *adapter,
++ uint8_t notify_format, uint64_t client_id)
+ {
+ struct mgmt_fe_session_ctx *session;
+
+- session = mgmt_fe_find_session_by_client_id(adapter, client_id);
++ session = fe_session_lookup_by_client_id(adapter, client_id);
+ if (session)
+- mgmt_fe_cleanup_session(&session);
++ fe_session_cleanup(&session);
+
+ session = XCALLOC(MTYPE_MGMTD_FE_SESSION,
+ sizeof(struct mgmt_fe_session_ctx));
+@@ -350,8 +341,7 @@ static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_
+ session->notify_format = notify_format;
+ session->txn_id = MGMTD_TXN_ID_NONE;
+ session->cfg_txn_id = MGMTD_TXN_ID_NONE;
+- mgmt_fe_adapter_lock(adapter);
+- mgmt_fe_sessions_add_tail(&adapter->fe_sessions, session);
++ LIST_INSERT_HEAD(&adapter->sessions, session, link);
+ if (!mgmt_fe_next_session_id)
+ mgmt_fe_next_session_id++;
+ session->session_id = mgmt_fe_next_session_id++;
+@@ -360,42 +350,49 @@ static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_
+ return session;
+ }
+
+-static int fe_adapter_send_native_msg(struct mgmt_fe_client_adapter *adapter,
+- void *msg, size_t len,
+- bool short_circuit_ok)
+-{
+- return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg,
+- len, NULL, short_circuit_ok);
+-}
+
+-static int fe_adapter_conn_send_error(struct msg_conn *conn,
+- uint64_t session_id, uint64_t req_id,
+- bool short_circuit_ok, int16_t error,
+- const char *errfmt, ...) PRINTFRR(6, 7);
+-static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id,
+- uint64_t req_id, bool short_circuit_ok,
+- int16_t error, const char *errfmt, ...)
+-{
+- va_list ap;
+- int ret;
++/* =============================== */
++/* Frontend Message (API) Handling */
++/* =============================== */
+
+- va_start(ap, errfmt);
+-
+- ret = vmgmt_msg_native_send_error(conn, session_id, req_id,
+- short_circuit_ok, error, errfmt, ap);
+- va_end(ap);
++/*
++ * Code structure: 3 functions per message type:
++ *
++ * 1) send reply back to FE client
++ * 2) process result from Txn module to send back to FE client, call (1)
++ * 3) handle request from FE client, either create TXN or call (1)
++ *
++ * For message types that don't require a transaction then (2) is elided.
++ *
++ * Txn's are used by any message that requires a fan-out to multiple backends:
++ *
++ * 1) FE handler creates Txn and calls Txn module to process request
++ * 2) Txn module creates Txn request determines backends to contact and sends
++ * them each a message.
++ * 3) Each backend client responds and the BE adapter calls TXN module
++ * 4) Txn module aggregates results and calls FE reply function to process
++ * send results back to FE client.
++ *
++ * .-> (BE Daemon) -> [BE adapter/Txn Handler]
++ * (1) (2) / o (3) \\ (4)
++ * [FE handler] => [TXN] ... o [TXN] => [FE reply]
++ * session \ o //
++ * `-> (BE Daemon) -> [BE adapter/Txn Handler]
++ */
+
+- return ret;
++static int fe_adapter_send_msg(struct mgmt_fe_client_adapter *adapter, void *msg, size_t len,
++ bool short_circuit_ok)
++{
++ return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg, len, NULL,
++ short_circuit_ok);
+ }
+
+-static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session,
+- uint64_t req_id, bool short_circuit_ok,
+- int16_t error, const char *errfmt, ...)
++static int fe_session_send_error(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++ bool short_circuit_ok, int16_t error, const char *errfmt, ...)
+ PRINTFRR(5, 6);
+
+-static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session,
+- uint64_t req_id, bool short_circuit_ok,
+- int16_t error, const char *errfmt, ...)
++static int fe_session_send_error(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++ bool short_circuit_ok, int16_t error, const char *errfmt, ...)
+ {
+ va_list ap;
+ int ret;
+@@ -409,300 +406,39 @@ static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session,
+ return ret;
+ }
+
+-
+-static void mgmt_fe_session_cfg_txn_clnup(struct event *thread)
+-{
+- struct mgmt_fe_session_ctx *session;
+-
+- session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread);
+-
+- mgmt_fe_session_cfg_txn_cleanup(session);
+-}
+-
+-static void mgmt_fe_session_show_txn_clnup(struct event *thread)
+-{
+- struct mgmt_fe_session_ctx *session;
+-
+- session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread);
+-
+- mgmt_fe_session_show_txn_cleanup(session);
+-}
+-
+-static void
+-mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session,
+- enum mgmt_session_event event)
+-{
+- struct timeval tv = {.tv_sec = 0,
+- .tv_usec = MGMTD_FE_MSG_PROC_DELAY_USEC};
+-
+- switch (event) {
+- case MGMTD_FE_SESSION_CFG_TXN_CLNUP:
+- event_add_timer_tv(mgmt_loop, mgmt_fe_session_cfg_txn_clnup,
+- session, &tv, &session->proc_cfg_txn_clnp);
+- break;
+- case MGMTD_FE_SESSION_SHOW_TXN_CLNUP:
+- event_add_timer_tv(mgmt_loop, mgmt_fe_session_show_txn_clnup,
+- session, &tv, &session->proc_show_txn_clnp);
+- break;
+- }
+-}
+-
+-static struct mgmt_fe_client_adapter *
+-mgmt_fe_find_adapter_by_fd(int conn_fd)
+-{
+- struct mgmt_fe_client_adapter *adapter;
+-
+- FOREACH_ADAPTER_IN_LIST (adapter) {
+- if (adapter->conn->fd == conn_fd)
+- return adapter;
+- }
+-
+- return NULL;
+-}
+-
+-static void mgmt_fe_adapter_delete(struct mgmt_fe_client_adapter *adapter)
+-{
+- struct mgmt_fe_session_ctx *session;
+- _dbg("deleting client adapter '%s'", adapter->name);
+-
+- /* TODO: notify about client disconnect for appropriate cleanup */
+- FOREACH_SESSION_IN_LIST (adapter, session)
+- mgmt_fe_cleanup_session(&session);
+- mgmt_fe_sessions_fini(&adapter->fe_sessions);
+-
+- assert(adapter->refcount == 1);
+- mgmt_fe_adapter_unlock(&adapter);
+-}
+-
+-static int mgmt_fe_adapter_notify_disconnect(struct msg_conn *conn)
+-{
+- struct mgmt_fe_client_adapter *adapter = conn->user;
+-
+- _dbg("notify disconnect for client adapter '%s'", adapter->name);
+-
+- mgmt_fe_adapter_delete(adapter);
+-
+- return 0;
+-}
+-
+-/**
+- * Send result of get-tree request back to the FE client.
+- *
+- * Args:
+- * session: the session.
+- * req_id: the request ID.
+- * short_circuit_ok: if allowed to short circuit the message.
+- * result_format: LYD_FORMAT for the sent output.
+- * tree: the tree to send, can be NULL which will send an empty tree.
+- * partial_error: if an error occurred during gathering results.
++/*
++ * Transaction (txn) Error - send an error back to the FE client and cleanup any
++ * in-progress txn.
+ *
+- * Return:
+- * Any error that occurs -- the message is likely not sent if non-zero.
++ * XXXchopps this is used in 2 places for GET-DATA and RPC errors from TXN
++ * module just adapt those 2 normal reply fucntions to take an error result and
++ * get rid of this function.
+ */
+-static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session,
+- uint64_t req_id, bool short_circuit_ok,
+- uint8_t result_type, uint32_t wd_options,
+- const struct lyd_node *tree,
+- int partial_error)
+-
++int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, bool short_circuit_ok,
++ int16_t error, const char *errstr)
+ {
+- struct mgmt_msg_tree_data *msg;
+- uint8_t **darrp = NULL;
+- int ret = 0;
+-
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0,
+- MTYPE_MSG_NATIVE_TREE_DATA);
+- msg->refer_id = session->session_id;
+- msg->req_id = req_id;
+- msg->code = MGMT_MSG_CODE_TREE_DATA;
+- msg->partial_error = partial_error;
+- msg->result_type = result_type;
+-
+- darrp = mgmt_msg_native_get_darrp(msg);
+- ret = yang_print_tree_append(darrp, tree, result_type,
+- (wd_options | LYD_PRINT_WITHSIBLINGS));
+- if (ret != LY_SUCCESS) {
+- _log_err("Error building get-tree result for client %s session-id %" PRIu64
+- " req-id %" PRIu64 " scok %d result type %u",
+- session->adapter->name, session->session_id, req_id, short_circuit_ok,
+- result_type);
+- goto done;
+- }
+-
+- _dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64
+- " scok %d result type %u len %u",
+- session->adapter->name, session->session_id, req_id, short_circuit_ok, result_type,
+- mgmt_msg_native_get_msg_len(msg));
+-
+- ret = fe_adapter_send_native_msg(session->adapter, msg,
+- mgmt_msg_native_get_msg_len(msg),
+- short_circuit_ok);
+-done:
+- mgmt_msg_native_free_msg(msg);
+-
+- return ret;
+-}
+-
+-static int fe_adapter_send_rpc_reply(struct mgmt_fe_session_ctx *session,
+- uint64_t req_id, uint8_t result_type,
+- const struct lyd_node *result)
+-{
+- struct mgmt_msg_rpc_reply *msg;
+- uint8_t **darrp = NULL;
++ struct mgmt_fe_session_ctx *session;
+ int ret;
+
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0,
+- MTYPE_MSG_NATIVE_RPC_REPLY);
+- msg->refer_id = session->session_id;
+- msg->req_id = req_id;
+- msg->code = MGMT_MSG_CODE_RPC_REPLY;
+- msg->result_type = result_type;
+-
+- if (result) {
+- darrp = mgmt_msg_native_get_darrp(msg);
+- ret = yang_print_tree_append(darrp, result, result_type, 0);
+- if (ret != LY_SUCCESS) {
+- _log_err("Error building rpc-reply result for client %s session-id %" PRIu64
+- " req-id %" PRIu64 " result type %u",
+- session->adapter->name, session->session_id, req_id, result_type);
+- goto done;
+- }
++ session = fe_session_by_txn_id(txn_id);
++ if (!session) {
++ _log_err("failed sending error for txn-id %" PRIu64 " session not found", txn_id);
++ return -ENOENT;
+ }
+
+- _dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 " len %u",
+- session->adapter->name, session->session_id, req_id, mgmt_msg_native_get_msg_len(msg));
+-
+- ret = fe_adapter_send_native_msg(session->adapter, msg,
+- mgmt_msg_native_get_msg_len(msg),
+- false);
+-done:
+- mgmt_msg_native_free_msg(msg);
+-
+- return ret;
+-}
+-
+-static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session,
+- uint64_t req_id, bool changed,
+- bool created, const char *xpath,
+- const char *data)
+-{
+- struct mgmt_msg_edit_reply *msg;
+- int ret;
++ ret = fe_session_send_error(session, req_id, false, error, "%s", errstr);
+
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0,
+- MTYPE_MSG_NATIVE_EDIT_REPLY);
+- msg->refer_id = session->session_id;
+- msg->req_id = req_id;
+- msg->changed = changed;
+- msg->created = created;
+- msg->code = MGMT_MSG_CODE_EDIT_REPLY;
+-
+- mgmt_msg_native_xpath_encode(msg, xpath);
+-
+- if (data)
+- mgmt_msg_native_append(msg, data, strlen(data) + 1);
+-
+- _dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64
+- " changed %u created %u len %u",
+- session->adapter->name, session->session_id, req_id, changed, created,
+- mgmt_msg_native_get_msg_len(msg));
+-
+- ret = fe_adapter_send_native_msg(session->adapter, msg,
+- mgmt_msg_native_get_msg_len(msg),
+- false);
+- mgmt_msg_native_free_msg(msg);
+-
+- return ret;
+-}
+-
+-static int fe_adapter_native_send_session_reply(struct mgmt_fe_client_adapter *adapter,
+- uint64_t session_id, uint64_t req_id, bool created)
+-{
+- struct mgmt_msg_session_reply *msg;
+- bool scok = adapter->conn->is_short_circuit;
+- int ret;
+-
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_session_reply, 0,
+- MTYPE_MSG_NATIVE_SESSION_REPLY);
+- msg->refer_id = session_id;
+- msg->req_id = req_id;
+- msg->code = MGMT_MSG_CODE_SESSION_REPLY;
+- msg->created = created;
+-
+- _dbg("Sending session-reply from adapter %s to session-id %Lu req-id %Lu created %u scok %u",
+- adapter->name, session_id, req_id, created, scok);
+-
+- ret = fe_adapter_send_native_msg(adapter, msg, mgmt_msg_native_get_msg_len(msg), scok);
+- mgmt_msg_native_free_msg(msg);
++ mgmt_destroy_txn(&session->txn_id);
+
+ return ret;
+ }
+
+-/**
+- * fe_adapter_handle_session_req() - Handle a session-req message from a FE client.
+- * @msg_raw: the message data.
+- * @msg_len: the length of the message data.
+- */
+-static void fe_adapter_handle_session_req(struct mgmt_fe_client_adapter *adapter, void *_msg,
+- size_t msg_len)
+-{
+- const struct mgmt_msg_session_req *msg = _msg;
+- struct mgmt_fe_session_ctx *session;
+- bool scok = adapter->conn->is_short_circuit;
+- uint64_t client_id;
+
+- _dbg("Got session-req is create %u req-id %Lu for refer-id %Lu notify-fmt %u from '%s'",
+- msg->refer_id == 0, msg->req_id, msg->refer_id, msg->notify_format, adapter->name);
++/* -------------- */
++/* COMMIT Message */
++/* -------------- */
+
+- /*
+- * It's important that any error has its refer_id set to 0 for create
+- * case and set to the passed in msg->refer_id otherwise (the destroy
+- * case). For non-error return pass the session_id for create or destroy.
+- */
+-
+- if (msg->refer_id) {
+- uint64_t session_id = msg->refer_id;
+-
+- session = mgmt_session_id2ctx(session_id);
+- if (!session) {
+- fe_adapter_conn_send_error(adapter->conn, session_id, msg->req_id, scok,
+- EINVAL,
+- "No session to delete for session-id: %" PRIu64,
+- session_id);
+- return;
+- }
+- fe_adapter_native_send_session_reply(adapter, session_id, msg->req_id, false);
+- mgmt_fe_cleanup_session(&session);
+- return;
+- }
+-
+- client_id = msg->req_id;
+-
+- /* Default notification format */
+- if (msg->notify_format && msg->notify_format > MGMT_MSG_FORMAT_LAST) {
+- fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL,
+- "Unrecognized notify format: %u", msg->notify_format);
+- return;
+- }
+-
+- /* See if we have a client name to register */
+- if (msg_len > sizeof(*msg)) {
+- if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) {
+- fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL,
+- "Corrupt session-req message rcvd from client-id: %" PRIu64,
+- client_id);
+- return;
+- }
+- _dbg("Set client-name to '%s'", msg->client_name);
+- strlcpy(adapter->name, msg->client_name, sizeof(adapter->name));
+- }
+-
+- session = mgmt_fe_create_session(adapter, msg->notify_format ?: DEFAULT_NOTIFY_FORMAT,
+- client_id);
+- fe_adapter_native_send_session_reply(adapter, session->session_id, msg->req_id, true);
+-}
+-
+-static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++static void fe_session_send_commit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id,
+ uint8_t source, uint8_t target, uint8_t action,
+ bool unlock)
+ {
+@@ -723,8 +459,7 @@ static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, ui
+ session->session_id, session->adapter->name, req_id, mgmt_ds_id2name(source),
+ mgmt_ds_id2name(target), action, unlock);
+
+- ret = fe_adapter_send_native_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg),
+- false);
++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false);
+ mgmt_msg_native_free_msg(msg);
+ if (ret) {
+ _log_err("Failed to send COMMIT_REPLY to session-id %Lu", session->session_id);
+@@ -732,19 +467,89 @@ static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, ui
+ }
+ }
+
+-/**
+- * fe_adapter_handle_commit() - Handle a commit message from a FE client.
+- * @msg_raw: the message data.
+- * @msg_len: the length of the message data.
+- */
+-static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void *_msg,
++int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, enum mgmt_ds_id src_ds_id,
++ enum mgmt_ds_id dst_ds_id, uint64_t req_id, bool validate_only,
++ bool unlock, enum mgmt_result result, const char *error_if_any)
++{
++ struct mgmt_fe_session_ctx *session;
++ uint8_t action;
++ int ret = 0;
++
++ /*
++ * When a session is deleted (e.g., disconnects) while there's an active
++ * TXN we can get a NULL return for the session_id after the TXN
++ * completes. A commit message never implicitly locks datastores --
++ * those locks are managed by the client (or cleaned up on session
++ * disconnect).
++ *
++ * However, for the implicit commit (legacy CLI/non-transactional) case
++ * the intention is that each change is made to the candidate and
++ * running at the same (i.e., as the user enters commands they take
++ * affect). So a failure to apply the change to running means we should
++ * back that change out of the candidate DS, and the user will be
++ * presented with an error message.
++ *
++ * So, if we have a failiure to apply an implicit commit, we
++ * should restore the candidate DS, and we can do this by copying
++ * running back over candidate. This doesn't work in the transactional
++ * case b/c the candidate may be made up from multiple changes and a
++ * failure of the latest change shouldn't invalidate all the previous
++ * valid changes which is what would happen if we copied running back
++ * over candidate.
++ */
++ session = fe_session_lookup(session_id);
++ if (session && session->cfg_txn_id && session->cfg_txn_id != txn_id)
++ session = NULL;
++
++ if (validate_only)
++ action = MGMT_MSG_COMMIT_VALIDATE;
++ else
++ action = MGMT_MSG_COMMIT_APPLY;
++
++ /*
++ * Restore the source from the dest in case of error with an implicit commit.
++ * Currently we use the unlock feedback to identify an implicit commit.
++ */
++ if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES && unlock)
++ mgmt_ds_copy_dss(mgmt_ds_get_ctx_by_id(mm, src_ds_id),
++ mgmt_ds_get_ctx_by_id(mm, dst_ds_id), false);
++
++ if (!session)
++ return -ENOENT;
++
++ if (mm->perf_stats_en)
++ gettimeofday(&session->adapter->cmt_stats.last_end, NULL);
++ fe_session_compute_commit_timers(&session->adapter->cmt_stats);
++
++ if (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES)
++ fe_session_send_commit_reply(session, req_id, src_ds_id, dst_ds_id, action, unlock);
++ else {
++ ret = fe_session_send_error(
++ session, req_id, false, EINVAL /* convert result */,
++ "commit failed session-id %Lu on %s req-id %Lu source-ds: %s target-ds: %s validate-only: %u: reason: '%s'",
++ session->session_id, session->adapter->name, req_id,
++ mgmt_ds_id2name(src_ds_id), mgmt_ds_id2name(dst_ds_id), validate_only,
++ error_if_any ?: "");
++ }
++
++ assert(session->cfg_txn_id == txn_id);
++ mgmt_destroy_txn(&session->cfg_txn_id);
++
++ return ret;
++}
++
++static void fe_session_handle_commit(struct mgmt_fe_session_ctx *session, void *_msg,
+ size_t msg_len)
+ {
+ struct mgmt_msg_commit *msg = _msg;
+ struct mgmt_ds_ctx *src_ds_ctx, *dst_ds_ctx;
++ uint64_t txn_id;
+
+- _dbg("Got COMMIT for source-ds: %s target-ds: %s action: %u on session-id %Lu from '%s'",
+- mgmt_ds_id2name(msg->source), mgmt_ds_id2name(msg->target), msg->action,
++ _dbg("Got COMMIT for source-ds: %s target-ds: %s action: %s on session-id %Lu from '%s'",
++ mgmt_ds_id2name(msg->source), mgmt_ds_id2name(msg->target),
++ msg->action == MGMT_MSG_COMMIT_VALIDATE ? "validate"
++ : msg->action == MGMT_MSG_COMMIT_ABORT ? "abort"
++ : "apply",
+ session->session_id, session->adapter->name);
+
+ if (mm->perf_stats_en)
+@@ -753,29 +558,52 @@ static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void *
+
+ /* Validate source and dest DS */
+ if (msg->source != MGMTD_DS_CANDIDATE || msg->target != MGMTD_DS_RUNNING) {
+- fe_adapter_send_error(session, msg->req_id, false, EINVAL,
++ fe_session_send_error(session, msg->req_id, false, EINVAL,
+ "source/target for commit must be candidate/running");
+ return;
+ }
+
+- /* User should have lock on both source and dest DS */
+- if (!session->ds_locked[msg->target] || !session->ds_locked[msg->source]) {
+- fe_adapter_send_error(session, msg->req_id, false, EBUSY,
+- "source/target not locked by session-id: %Lu on '%s'",
++ src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->source);
++ dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->target);
++
++ if (mgmt_ds_is_txn_locked(src_ds_ctx, &txn_id) ||
++ mgmt_ds_is_txn_locked(dst_ds_ctx, &txn_id)) {
++ fe_session_send_error(session, msg->req_id, false, EBUSY,
++ "source/target datastore is locked by another transaction txn-id: %Lu",
++ txn_id);
++ return;
++ }
++
++ /* User must always have lock on source */
++ if (!session->ds_locked[msg->source]) {
++ fe_session_send_error(session, msg->req_id, false, EBUSY,
++ "source not locked by session-id: %Lu on '%s'",
+ session->session_id, session->adapter->name);
+ return;
+ }
+
++ /* For apply/abort user must also have lock on target */
++ if (msg->action != MGMT_MSG_COMMIT_VALIDATE && !session->ds_locked[msg->target]) {
++ fe_session_send_error(session, msg->req_id, false, EBUSY,
++ "target not locked for apply/abort by session-id: %Lu on '%s'",
++ session->session_id, session->adapter->name);
++ return;
++ }
++
++ /*
++ * We get an exisitng cfg_txn_id if a check/validate was done first then
++ * an apply/abort later.
++ */
+ if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) {
+ /* as we have the lock no-one else should have a config txn */
+- assert(!mgmt_config_txn_in_progress());
++ assert(!mgmt_txn_config_in_progress());
+
+ /*
+ * Start a CONFIG Transaction (if not started already)
+ */
+ session->cfg_txn_id = mgmt_create_txn(session->session_id, MGMTD_TXN_TYPE_CONFIG);
+ if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
+- fe_adapter_send_error(session, msg->req_id, false, ENOMEM,
++ fe_session_send_error(session, msg->req_id, false, ENOMEM,
+ "failed to create config transaction for session-id: %Lu on '%s'",
+ session->session_id, session->adapter->name);
+ return;
+@@ -787,23 +615,19 @@ static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void *
+ /*
+ * Create COMMIT Config request under the transaction
+ */
+- src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->source);
+- dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->target);
+- assert(src_ds_ctx && dst_ds_ctx);
+- if (mgmt_txn_send_commit_config_req(session->cfg_txn_id, msg->req_id, msg->source,
+- src_ds_ctx, msg->target, dst_ds_ctx,
+- msg->action == MGMT_MSG_COMMIT_VALIDATE,
+- msg->action == MGMT_MSG_COMMIT_ABORT,
+- false /* implicit */, msg->unlock, NULL) != 0) {
+- fe_adapter_send_error(session, msg->req_id, false, EINVAL,
+- "failed to create config request under txn-id: %Lu for session-id: %Lu on '%s'",
+- session->cfg_txn_id, session->session_id,
+- session->adapter->name);
+- }
++ mgmt_txn_send_commit_config_req(session->cfg_txn_id, msg->req_id, msg->source, src_ds_ctx,
++ msg->target, dst_ds_ctx,
++ msg->action == MGMT_MSG_COMMIT_VALIDATE,
++ msg->action == MGMT_MSG_COMMIT_ABORT, false /* implicit */,
++ msg->unlock, NULL);
+ }
+
+-static int fe_adapter_native_send_lock_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id,
+- uint8_t datastore, bool lock, bool short_circuit_ok)
++/* ------------ */
++/* LOCK Message */
++/* ------------ */
++
++static int fe_session_send_lock_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++ uint8_t datastore, bool lock, bool short_circuit_ok)
+ {
+ struct mgmt_msg_lock_reply *msg;
+ int ret;
+@@ -819,25 +643,21 @@ static int fe_adapter_native_send_lock_reply(struct mgmt_fe_session_ctx *session
+ session->adapter->name, session->session_id, req_id, datastore, lock,
+ short_circuit_ok);
+
+- ret = fe_adapter_send_native_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg),
+- short_circuit_ok);
++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg),
++ short_circuit_ok);
+ mgmt_msg_native_free_msg(msg);
+
+ return ret;
+ }
+
+-/**
+- * fe_adapter_handle_lock() - Handle a session-req message from a FE client.
+- * @msg_raw: the message data.
+- * @msg_len: the length of the message data.
+- */
+-static void fe_adapter_handle_lock(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len)
++static void fe_session_handle_lock(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len)
+ {
+ const struct mgmt_msg_lock *msg = _msg;
+ struct mgmt_ds_ctx *ds_ctx;
+ bool short_circuit_ok = session->adapter->conn->is_short_circuit;
+ uint8_t datastore = msg->datastore;
+ uint64_t lock_session;
++ uint64_t txn_id;
+ bool lock = msg->lock;
+ int ret;
+
+@@ -845,48 +665,111 @@ static void fe_adapter_handle_lock(struct mgmt_fe_session_ctx *session, void *_m
+ mgmt_ds_id2name(datastore), msg->refer_id, session->adapter->name);
+
+ if (datastore != MGMTD_DS_CANDIDATE && datastore != MGMTD_DS_RUNNING) {
+- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EINVAL,
++ fe_session_send_error(session, msg->req_id, short_circuit_ok, EINVAL,
+ "Lock/Unlock on DS other than candidate or running DS not supported");
+ return;
+ }
+
+- ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore);
+- assert(ds_ctx);
++ ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore);
++ assert(ds_ctx);
++
++ if (lock && mgmt_ds_is_locked(ds_ctx, &lock_session, &txn_id) &&
++ lock_session != session->session_id) {
++ fe_session_send_error(session, msg->req_id, short_circuit_ok, EBUSY,
++ "Lock already taken on datastore %s by session: %Lu txn-id: %Lu",
++ mgmt_ds_id2name(datastore), lock_session, txn_id);
++ return;
++ } else if (lock) {
++ ret = mgmt_fe_session_write_lock_ds(datastore, ds_ctx, session);
++ if (ret) {
++ fe_session_send_error(session, msg->req_id, short_circuit_ok, EBUSY,
++ "Unexpected error %d trying to lock datastore by session-id: %Lu",
++ ret, session->session_id);
++ return;
++ }
++ } else {
++ /* unlock even if one or both of the lock indicators is wrong */
++ mgmt_fe_session_unlock_ds(datastore, ds_ctx, session);
++ }
++
++ if (fe_session_send_lock_reply(session, msg->req_id, msg->datastore, msg->lock,
++ short_circuit_ok)) {
++ assert(!short_circuit_ok);
++ _log_err("Failed to send LOCK_REPLY to session-id %Lu", session->session_id);
++ msg_conn_disconnect(session->adapter->conn, false);
++ }
++}
++
++/* ---------------- */
++/* GET-DATA Message */
++/* ---------------- */
++
++/*
++ * Send result of get-tree request back to the FE client.
++ */
++static int fe_session_send_tree_data(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++ bool short_circuit_ok, uint8_t result_type,
++ uint32_t wd_options, const struct lyd_node *tree,
++ int partial_error)
++
++{
++ struct mgmt_msg_tree_data *msg;
++ uint8_t **darrp = NULL;
++ int ret = 0;
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, MTYPE_MSG_NATIVE_TREE_DATA);
++ msg->refer_id = session->session_id;
++ msg->req_id = req_id;
++ msg->code = MGMT_MSG_CODE_TREE_DATA;
++ msg->partial_error = partial_error;
++ msg->result_type = result_type;
++
++ darrp = mgmt_msg_native_get_darrp(msg);
++ ret = yang_print_tree_append(darrp, tree, result_type,
++ (wd_options | LYD_PRINT_WITHSIBLINGS));
++ if (ret != LY_SUCCESS) {
++ _log_err("Error building get-tree result for client %s session-id %" PRIu64
++ " req-id %" PRIu64 " scok %d result type %u",
++ session->adapter->name, session->session_id, req_id, short_circuit_ok,
++ result_type);
++ goto done;
++ }
++
++ _dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64
++ " scok %d result type %u len %u",
++ session->adapter->name, session->session_id, req_id, short_circuit_ok, result_type,
++ mgmt_msg_native_get_msg_len(msg));
++
++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg),
++ short_circuit_ok);
++done:
++ mgmt_msg_native_free_msg(msg);
++
++ return ret;
++}
+
+- if (lock && mgmt_ds_is_locked(ds_ctx, &lock_session) &&
+- lock_session != session->session_id) {
+- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EBUSY,
+- "Lock already taken on datastore by session: %Lu",
+- lock_session);
++void mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id,
++ LYD_FORMAT result_type, uint32_t wd_options,
++ const struct lyd_node *tree, int partial_error,
++ bool short_circuit_ok)
++{
++ struct mgmt_fe_session_ctx *session;
++
++ session = fe_session_lookup(session_id);
++ if (!session)
+ return;
+- } else if (lock) {
+- ret = mgmt_fe_session_write_lock_ds(datastore, ds_ctx, session);
+- if (ret) {
+- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EBUSY,
+- "Unexpected error %d trying to lock datastore by session-id: %Lu",
+- ret, session->session_id);
+- return;
+- }
+- } else {
+- /* unlock even if one or both of the lock indicators is wrong */
+- mgmt_fe_session_unlock_ds(datastore, ds_ctx, session);
+- }
+
+- if (fe_adapter_native_send_lock_reply(session, msg->req_id, msg->datastore, msg->lock,
+- short_circuit_ok)) {
+- assert(!short_circuit_ok);
+- _log_err("Failed to send LOCK_REPLY to session-id %Lu", session->session_id);
+- msg_conn_disconnect(session->adapter->conn, false);
+- }
++ /* XXXchopps why do we care about this? Why not allow multple? */
++ assert(session->txn_id == txn_id);
++
++ if (fe_session_send_tree_data(session, req_id, short_circuit_ok, result_type, wd_options,
++ tree, partial_error))
++ fe_session_send_error(session, req_id, false, -EIO,
++ "Failed sending GET-DATA reply");
++ mgmt_destroy_txn(&session->txn_id);
+ }
+
+-/**
+- * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client.
+- * @session: the client session.
+- * @msg_raw: the message data.
+- * @msg_len: the length of the message data.
+- */
+-static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void *_msg,
++static void fe_session_handle_get_data(struct mgmt_fe_session_ctx *session, void *_msg,
+ size_t msg_len)
+ {
+ struct mgmt_msg_get_data *msg = _msg;
+@@ -905,14 +788,14 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ session->adapter->name, session->session_id, msg->req_id);
+
+ if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) {
+- fe_adapter_send_error(session, req_id, false, -EINVAL,
++ fe_session_send_error(session, req_id, false, -EINVAL,
+ "Invalid message rcvd from session-id: %" PRIu64,
+ session->session_id);
+ goto done;
+ }
+
+ if (session->txn_id != MGMTD_TXN_ID_NONE) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
++ fe_session_send_error(session, req_id, false, -EINPROGRESS,
+ "Transaction in progress txn-id: %" PRIu64
+ " for session-id: %" PRIu64,
+ session->txn_id, session->session_id);
+@@ -933,7 +816,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ wd_options = LYD_PRINT_WD_IMPL_TAG;
+ break;
+ default:
+- fe_adapter_send_error(session, req_id, false, -EINVAL,
++ fe_session_send_error(session, req_id, false, -EINVAL,
+ "Invalid defaults value %u for session-id: %" PRIu64,
+ msg->defaults, session->session_id);
+ goto done;
+@@ -955,13 +838,13 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ err = ly_ctx_get_yanglib_data(ly_native_ctx, &ylib, "%u",
+ ly_ctx_get_change_count(ly_native_ctx));
+ if (err) {
+- fe_adapter_send_error(session, req_id, false, err,
++ fe_session_send_error(session, req_id, false, err,
+ "Error getting yang-library data, session-id: %" PRIu64
+ " error: %s",
+ session->session_id, ly_last_errmsg());
+ } else if (nb_oper_is_yang_lib_query(msg->xpath)) {
+ yang_lyd_trim_xpath(&ylib, msg->xpath);
+- (void)fe_adapter_send_tree_data(session, req_id, false, msg->result_type,
++ (void)fe_session_send_tree_data(session, req_id, false, msg->result_type,
+ wd_options, ylib, 0);
+ goto done;
+ }
+@@ -978,7 +861,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ ds_id = MGMTD_DS_OPERATIONAL;
+ break;
+ default:
+- fe_adapter_send_error(session, req_id, false, -EINVAL,
++ fe_session_send_error(session, req_id, false, -EINVAL,
+ "Unsupported datastore %" PRIu8
+ " requested from session-id: %" PRIu64,
+ msg->datastore, session->session_id);
+@@ -988,7 +871,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes,
+ &simple_xpath);
+ if (err) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
++ fe_session_send_error(session, req_id, false, -EINPROGRESS,
+ "XPath doesn't resolve for session-id: %" PRIu64,
+ session->session_id);
+ goto done;
+@@ -1002,8 +885,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ _dbg("No backends provide xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64,
+ msg->xpath, session->txn_id, session->session_id);
+
+- fe_adapter_send_tree_data(session, req_id, false,
+- msg->result_type, wd_options, NULL, 0);
++ fe_session_send_tree_data(session, req_id, false, msg->result_type, wd_options,
++ NULL, 0);
+ goto done;
+ }
+
+@@ -1013,8 +896,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ /* Start a SHOW Transaction */
+ session->txn_id = mgmt_create_txn(session->session_id,
+ MGMTD_TXN_TYPE_SHOW);
+- if (session->txn_id == MGMTD_SESSION_ID_NONE) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
++ if (session->txn_id == MGMTD_TXN_ID_NONE) {
++ fe_session_send_error(session, req_id, false, -EINPROGRESS,
+ "failed to create a 'show' txn");
+ goto done;
+ }
+@@ -1028,7 +911,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void
+ if (ret) {
+ /* destroy the just created txn */
+ mgmt_destroy_txn(&session->txn_id);
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
++ fe_session_send_error(session, req_id, false, -EINPROGRESS,
+ "failed to create a 'show' txn");
+ }
+ done:
+@@ -1037,120 +920,215 @@ done:
+ darr_free(snodes);
+ }
+
+-static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len)
++/* ------------ */
++/* EDIT Message */
++/* ------------ */
++
++static int fe_session_send_edit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++ bool changed, bool created, const char *xpath,
++ const char *data)
++{
++ struct mgmt_msg_edit_reply *msg;
++ int ret;
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, MTYPE_MSG_NATIVE_EDIT_REPLY);
++ msg->refer_id = session->session_id;
++ msg->req_id = req_id;
++ msg->changed = changed;
++ msg->created = created;
++ msg->code = MGMT_MSG_CODE_EDIT_REPLY;
++
++ mgmt_msg_native_xpath_encode(msg, xpath);
++
++ if (data)
++ mgmt_msg_native_append(msg, data, strlen(data) + 1);
++
++ _dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64
++ " changed %u created %u len %u",
++ session->adapter->name, session->session_id, req_id, changed, created,
++ mgmt_msg_native_get_msg_len(msg));
++
++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false);
++ mgmt_msg_native_free_msg(msg);
++
++ return ret;
++}
++
++
++int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id,
++ bool unlock, bool commit, struct mgmt_edit_req **edit,
++ enum mgmt_result result, const char *errstr)
++{
++ struct mgmt_fe_session_ctx *session;
++ const enum mgmt_ds_id can_id = MGMTD_DS_CANDIDATE;
++ const enum mgmt_ds_id run_id = MGMTD_DS_RUNNING;
++ struct mgmt_ds_ctx *can_ds = mgmt_ds_get_ctx_by_id(mm, can_id);
++ struct mgmt_ds_ctx *run_ds = mgmt_ds_get_ctx_by_id(mm, run_id);
++ int ret;
++
++ /*
++ * When a session is deleted (e.g., disconnects) while there's an
++ * active TXN we can get a NULL return here when the TXN completes. We
++ * still want to do any cleanup that the session cleanup could not
++ * accomplish b/c of the outstanding TXN.
++ */
++ session = fe_session_lookup(session_id);
++ if (session && session->cfg_txn_id && session->cfg_txn_id != txn_id)
++ session = NULL;
++
++ /*
++ * Deal with the backup candidate config. If the edit was successful we
++ * free the backup. If it failed we restore the datastore from the
++ * backup.
++ */
++ if (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES)
++ nb_config_free((*edit)->nb_backup);
++ else
++ mgmt_ds_restore_nb_config(can_ds, (*edit)->nb_backup);
++
++ if (!session)
++ return -ENOENT;
++
++ if ((*edit)->unlock_running)
++ mgmt_fe_session_unlock_ds(run_id, run_ds, session);
++ if ((*edit)->unlock_candidate)
++ mgmt_fe_session_unlock_ds(can_id, can_ds, session);
++
++
++ if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES)
++ /* Fix the error */
++ ret = fe_session_send_error(session, req_id, false, -EINVAL, "%s", errstr);
++ else
++ ret = fe_session_send_edit_reply(session, req_id,
++ result == MGMTD_SUCCESS /*changed*/,
++ (*edit)->created, (*edit)->xpath_created, errstr);
++ XFREE(MTYPE_MGMTD_TXN_REQ, *edit);
++
++ assert(session->cfg_txn_id == txn_id);
++ mgmt_destroy_txn(&session->cfg_txn_id);
++
++ return ret;
++}
++
++static void fe_session_handle_edit(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len)
+ {
+ struct mgmt_msg_edit *msg = _msg;
+- enum mgmt_ds_id ds_id, rds_id;
+- struct mgmt_ds_ctx *ds_ctx, *rds_ctx;
++ const enum mgmt_ds_id can_id = MGMTD_DS_CANDIDATE;
++ const enum mgmt_ds_id run_id = MGMTD_DS_RUNNING;
++ struct mgmt_ds_ctx *can_ds = mgmt_ds_get_ctx_by_id(mm, can_id);
++ struct mgmt_ds_ctx *run_ds = mgmt_ds_get_ctx_by_id(mm, run_id);
++ struct mgmt_edit_req *edit;
++ struct nb_config *nb_config;
++ uint64_t txn_id = MGMTD_TXN_ID_NONE;
++ bool implicit_can_lock = false;
++ bool implicit_run_lock = false;
+ const char *xpath, *data;
+- bool lock, commit;
++ char errstr[BUFSIZ];
++ bool commit;
+ int ret;
+
+- lock = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_LOCK);
++ /* grab the deprecated commit flag -- the lock flag isn't required at all */
+ commit = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_COMMIT);
+
+- if (lock && commit && msg->datastore == MGMT_MSG_DATASTORE_RUNNING)
+- ;
+- else if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) {
+- fe_adapter_send_error(session, msg->req_id, false, -EINVAL,
++ /*
++ * Validate input args and obtain any needed locks
++ */
++
++ commit = commit || msg->datastore == MGMT_MSG_DATASTORE_RUNNING;
++ if (!commit && msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) {
++ fe_session_send_error(session, msg->req_id, false, -EINVAL,
+ "Unsupported datastore");
+ return;
+ }
+
++ /* Decode the target xpath and config changes */
+ xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data);
+ if (!xpath) {
+- fe_adapter_send_error(session, msg->req_id, false, -EINVAL,
+- "Invalid message");
++ fe_session_send_error(session, msg->req_id, false, -EINVAL, "Invalid message");
+ return;
+ }
+
+- ds_id = MGMTD_DS_CANDIDATE;
+- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id);
+- assert(ds_ctx);
+-
+- rds_id = MGMTD_DS_RUNNING;
+- rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id);
+- assert(rds_ctx);
++ /* If committing, make sure no other txn has locks on the datastores */
++ if (commit &&
++ (mgmt_ds_is_txn_locked(can_ds, &txn_id) || mgmt_ds_is_txn_locked(run_ds, &txn_id))) {
++ fe_session_send_error(session, msg->req_id, false, -EBUSY,
++ "datastores are locked by another transaction txn-id: %Lu",
++ txn_id);
++ return;
++ }
+
+- if (lock) {
+- if (mgmt_fe_session_write_lock_ds(ds_id, ds_ctx, session)) {
+- fe_adapter_send_error(session, msg->req_id, false,
+- -EBUSY,
++ /* We always ensure the candidate DS is locked */
++ if (!session->ds_locked[can_id]) {
++ if (mgmt_fe_session_write_lock_ds(can_id, can_ds, session)) {
++ fe_session_send_error(session, msg->req_id, false, -EBUSY,
+ "Candidate DS is locked by another session");
+ return;
+ }
++ implicit_can_lock = true;
++ }
+
+- if (commit) {
+- if (mgmt_fe_session_write_lock_ds(rds_id, rds_ctx,
+- session)) {
+- mgmt_fe_session_unlock_ds(ds_id, ds_ctx,
+- session);
+- fe_adapter_send_error(
+- session, msg->req_id, false, -EBUSY,
+- "Running DS is locked by another session");
+- return;
+- }
+- }
+- } else {
+- if (!session->ds_locked[ds_id]) {
+- fe_adapter_send_error(session, msg->req_id, false,
+- -EBUSY,
+- "Candidate DS is not locked");
++ /* And if modifying running, ensure it is locked too */
++ if (commit && !session->ds_locked[run_id]) {
++ if (mgmt_fe_session_write_lock_ds(run_id, run_ds, session)) {
++ if (implicit_can_lock)
++ mgmt_fe_session_unlock_ds(can_id, can_ds, session);
++ fe_session_send_error(session, msg->req_id, false, -EBUSY,
++ "Running DS is locked by another session");
+ return;
+ }
+-
+- if (commit) {
+- if (!session->ds_locked[rds_id]) {
+- fe_adapter_send_error(session, msg->req_id,
+- false, -EBUSY,
+- "Running DS is not locked");
+- return;
+- }
+- }
++ implicit_run_lock = true;
+ }
+
+- session->cfg_txn_id = mgmt_create_txn(session->session_id,
+- MGMTD_TXN_TYPE_CONFIG);
+- if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
+- if (lock) {
+- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
+- if (commit)
+- mgmt_fe_session_unlock_ds(rds_id, rds_ctx,
+- session);
++ /*
++ * Everythign valid and setup, proceed to make the edit.
++ */
++
++ errstr[0] = '\0';
++ nb_config = mgmt_ds_get_nb_config(can_ds);
++
++ edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req));
++ edit->unlock_candidate = implicit_can_lock;
++ edit->unlock_running = implicit_run_lock;
++ edit->nb_backup = nb_config_dup(nb_config); /* keep a backup of candidate */
++
++ /* Make edits to the candidate DS */
++ ret = nb_candidate_edit_tree(nb_config, msg->operation, msg->request_type, xpath, data,
++ &edit->created, edit->xpath_created, errstr, sizeof(errstr));
++ if (ret && ret == NB_ERR_NO_CHANGES)
++ ret = NB_OK;
++ else if (ret)
++ _dbg("Edit failed for txn-id %Lu req-id %Lu: %s: restoring candidate", txn_id,
++ msg->req_id, errstr[0] ? errstr : "unknown reason");
++ else if (commit) {
++ /* Get a TXN for the commit */
++ txn_id = mgmt_create_txn(session->session_id, MGMTD_TXN_TYPE_CONFIG);
++ if (txn_id == MGMTD_SESSION_ID_NONE) {
++ ret = NB_ERR_EXISTS; /* should not happen as we have the locks */
++ goto reply;
+ }
+- fe_adapter_send_error(session, msg->req_id, false, -EBUSY,
+- "Failed to create a configuration transaction");
+- return;
+- }
++ session->cfg_txn_id = txn_id;
+
+- _dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64, session->cfg_txn_id,
+- session->session_id);
++ _dbg("Created new config txn-id: %Lu for session-id: %Lu", txn_id,
++ session->session_id);
+
+- ret = mgmt_txn_send_edit(session->cfg_txn_id, msg->req_id, ds_id,
+- ds_ctx, rds_id, rds_ctx, lock, commit,
+- msg->request_type, msg->flags, msg->operation,
+- xpath, data);
+- if (ret) {
+- /* destroy the just created txn */
+- mgmt_destroy_txn(&session->cfg_txn_id);
+- if (lock) {
+- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
+- if (commit)
+- mgmt_fe_session_unlock_ds(rds_id, rds_ctx,
+- session);
+- }
+- fe_adapter_send_error(session, msg->req_id, false, -EBUSY,
+- "Failed to create a configuration transaction");
++ /* And this is modifying the running */
++ mgmt_txn_send_commit_config_req(txn_id, msg->req_id, can_id, can_ds, run_id, run_ds,
++ false, false, true /* implicit */, false, edit);
++ return;
+ }
++reply:
++ mgmt_fe_adapter_send_edit_reply(session->session_id, txn_id, msg->req_id, false, commit,
++ &edit, nb_error_to_mgmt_result(ret), errstr);
+ }
+
+-/**
+- * fe_adapter_handle_notify_select() - Handle an Notify Select message.
+- * @session: the client session.
+- * @_msg: the message data.
+- * @msg_len: the length of the message data.
++/* --------------------- */
++/* NOTIFY-SELECT Message */
++/* --------------------- */
++
++/*
++ * Handle an Notify Select message - there's no reply for this message.
+ */
+-static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, void *_msg,
++static void fe_session_handle_notify_select(struct mgmt_fe_session_ctx *session, void *_msg,
+ size_t msg_len)
+ {
+ struct mgmt_msg_notify_select *msg = _msg;
+@@ -1162,13 +1140,12 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session,
+ char *selstr = NULL;
+ uint64_t clients = 0;
+ uint64_t all_matched = 0, rm_clients = 0;
+- uint ret = NB_OK;
+
+
+ if (msg_len >= sizeof(*msg)) {
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
+ if (!selectors) {
+- fe_adapter_send_error(session, req_id, false, -EINVAL, "Invalid message");
++ fe_session_send_error(session, req_id, false, -EINVAL, "Invalid message");
+ return;
+ }
+ }
+@@ -1177,7 +1154,7 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session,
+ darr_foreach_p (selectors, sp) {
+ nb_nodes = nb_nodes_find(*sp);
+ if (!nb_nodes) {
+- fe_adapter_send_error(session, req_id, false, -EINVAL,
++ fe_session_send_error(session, req_id, false, -EINVAL,
+ "Selector doesn't resolve to a node: %s", *sp);
+ darr_free_free(selectors);
+ return;
+@@ -1236,35 +1213,22 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session,
+ session->session_id);
+ goto done;
+ }
+- if (!(clients | rm_clients)) {
++ if (!(clients | rm_clients))
+ _dbg("No backends to newly notify for selectors: '%s' session-id: %Lu", selstr,
+ session->session_id);
+- } else {
+- /*
+- * First send a message to set the selectors on the changed clients.
+- */
+- ret = mgmt_txn_send_notify_selectors(req_id, MGMTD_SESSION_ID_NONE,
+- (clients | rm_clients),
+- msg->replace ? NULL : selectors);
+- if (ret) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
+- "Failed to create a NOTIFY_SELECT transaction");
+- }
+- }
++ else
++ /* Send a message to set the selectors on the changed clients */
++ mgmt_txn_send_notify_selectors(req_id, MGMTD_SESSION_ID_NONE,
++ (clients | rm_clients),
++ msg->replace ? NULL : selectors);
+
+- if (ret != NB_OK || !all_matched || !selectors)
++ if (!all_matched || !selectors)
+ goto done;
+
+ _dbg("Created new push for session-id: %Lu", session->session_id);
+
+- /*
+- * Send a second message requesting a full state dump
+- */
+- ret = mgmt_txn_send_notify_selectors(req_id, session->session_id, all_matched, selectors);
+- if (ret) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
+- "Failed to create a NOTIFY_SELECT transaction");
+- }
++ /* Send a second message requesting a full state dump for the session */
++ mgmt_txn_send_notify_selectors(req_id, session->session_id, all_matched, selectors);
+ done:
+ if (session->notify_xpaths != selectors)
+ darr_free(selectors);
+@@ -1272,33 +1236,82 @@ done:
+ XFREE(MTYPE_TMP, selstr);
+ }
+
+-/**
+- * fe_adapter_handle_rpc() - Handle an RPC message from an FE client.
+- * @session: the client session.
+- * @_msg: the message data.
+- * @msg_len: the length of the message data.
+- */
+-static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len)
++/* ----------- */
++/* RPC Message */
++/* ----------- */
++
++static int fe_session_send_rpc_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id,
++ uint8_t result_type, const struct lyd_node *result)
++{
++ struct mgmt_msg_rpc_reply *msg;
++ uint8_t **darrp = NULL;
++ int ret;
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, MTYPE_MSG_NATIVE_RPC_REPLY);
++ msg->refer_id = session->session_id;
++ msg->req_id = req_id;
++ msg->code = MGMT_MSG_CODE_RPC_REPLY;
++ msg->result_type = result_type;
++
++ if (result) {
++ darrp = mgmt_msg_native_get_darrp(msg);
++ ret = yang_print_tree_append(darrp, result, result_type, 0);
++ if (ret != LY_SUCCESS) {
++ _log_err("Error building rpc-reply result for client %s session-id %" PRIu64
++ " req-id %" PRIu64 " result type %u",
++ session->adapter->name, session->session_id, req_id, result_type);
++ goto done;
++ }
++ }
++
++ _dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64
++ " len %u",
++ session->adapter->name, session->session_id, req_id, mgmt_msg_native_get_msg_len(msg));
++
++ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false);
++done:
++ mgmt_msg_native_free_msg(msg);
++
++ return ret;
++}
++
++void mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id,
++ LYD_FORMAT result_type, const struct lyd_node *result)
++{
++ struct mgmt_fe_session_ctx *session;
++
++ session = fe_session_lookup(session_id);
++ if (!session)
++ return;
++
++ /* XXXchopps why do we care about this? Why not allow multple? */
++ assert(session->txn_id == txn_id);
++
++ if (fe_session_send_rpc_reply(session, req_id, result_type, result))
++ fe_session_send_error(session, req_id, false, -EIO, "Failed sending RPC reply");
++
++ mgmt_destroy_txn(&session->txn_id);
++}
++
++static void fe_session_handle_rpc(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len)
+ {
+ struct mgmt_msg_rpc *msg = _msg;
+ const struct lysc_node *snode;
+ const char *xpath, *data;
+ uint64_t req_id = msg->req_id;
+ uint64_t clients;
+- int ret;
+
+ _dbg("Received RPC request from client %s for session-id %" PRIu64 " req-id %" PRIu64,
+ session->adapter->name, session->session_id, msg->req_id);
+
+ xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data);
+ if (!xpath) {
+- fe_adapter_send_error(session, req_id, false, -EINVAL,
+- "Invalid message");
++ fe_session_send_error(session, req_id, false, -EINVAL, "Invalid message");
+ return;
+ }
+
+ if (session->txn_id != MGMTD_TXN_ID_NONE) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
++ fe_session_send_error(session, req_id, false, -EINPROGRESS,
+ "Transaction in progress txn-id: %" PRIu64
+ " for session-id: %" PRIu64,
+ session->txn_id, session->session_id);
+@@ -1307,13 +1320,12 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms
+
+ snode = lys_find_path(ly_native_ctx, NULL, xpath, 0);
+ if (!snode) {
+- fe_adapter_send_error(session, req_id, false, -ENOENT,
+- "No such path: %s", xpath);
++ fe_session_send_error(session, req_id, false, -ENOENT, "No such path: %s", xpath);
+ return;
+ }
+
+ if (snode->nodetype != LYS_RPC && snode->nodetype != LYS_ACTION) {
+- fe_adapter_send_error(session, req_id, false, -EINVAL,
++ fe_session_send_error(session, req_id, false, -EINVAL,
+ "Not an RPC or action path: %s", xpath);
+ return;
+ }
+@@ -1324,7 +1336,7 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms
+ _dbg("No backends implement xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64,
+ xpath, session->txn_id, session->session_id);
+
+- fe_adapter_send_error(session, req_id, false, -ENOENT,
++ fe_session_send_error(session, req_id, false, -ENOENT,
+ "No backends implement xpath: %s", xpath);
+ return;
+ }
+@@ -1333,7 +1345,7 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms
+ session->txn_id = mgmt_create_txn(session->session_id,
+ MGMTD_TXN_TYPE_RPC);
+ if (session->txn_id == MGMTD_SESSION_ID_NONE) {
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
++ fe_session_send_error(session, req_id, false, -EINPROGRESS,
+ "Failed to create an RPC transaction");
+ return;
+ }
+@@ -1342,27 +1354,140 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms
+ session->session_id);
+
+ /* Create an RPC request under the transaction */
+- ret = mgmt_txn_send_rpc(session->txn_id, req_id, clients,
+- msg->request_type, xpath, data,
+- mgmt_msg_native_data_len_decode(msg, msg_len));
+- if (ret) {
+- /* destroy the just created txn */
+- mgmt_destroy_txn(&session->txn_id);
+- fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
+- "Failed to create an RPC transaction");
++ mgmt_txn_send_rpc(session->txn_id, req_id, clients, msg->request_type, xpath, data,
++ mgmt_msg_native_data_len_decode(msg, msg_len));
++}
++
++/* -------------------- */
++/* New Session Requests */
++/* -------------------- */
++
++static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, uint64_t req_id,
++ bool short_circuit_ok, int16_t error, const char *errfmt,
++ ...) PRINTFRR(6, 7);
++static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, uint64_t req_id,
++ bool short_circuit_ok, int16_t error, const char *errfmt, ...)
++{
++ va_list ap;
++ int ret;
++
++ va_start(ap, errfmt);
++
++ ret = vmgmt_msg_native_send_error(conn, session_id, req_id, short_circuit_ok, error,
++ errfmt, ap);
++ va_end(ap);
++
++ return ret;
++}
++
++static int fe_adapter_send_session_reply(struct mgmt_fe_client_adapter *adapter,
++ uint64_t session_id, uint64_t req_id, bool created)
++{
++ struct mgmt_msg_session_reply *msg;
++ bool scok = adapter->conn->is_short_circuit;
++ int ret;
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_session_reply, 0,
++ MTYPE_MSG_NATIVE_SESSION_REPLY);
++ msg->refer_id = session_id;
++ msg->req_id = req_id;
++ msg->code = MGMT_MSG_CODE_SESSION_REPLY;
++ msg->created = created;
++
++ _dbg("Sending session-reply from adapter %s to session-id %Lu req-id %Lu created %u scok %u",
++ adapter->name, session_id, req_id, created, scok);
++
++ ret = fe_adapter_send_msg(adapter, msg, mgmt_msg_native_get_msg_len(msg), scok);
++ mgmt_msg_native_free_msg(msg);
++
++ return ret;
++}
++
++/*
++ * Handle a session-req message from a FE client.
++ */
++static void fe_adapter_handle_session_req(struct mgmt_fe_client_adapter *adapter, void *_msg,
++ size_t msg_len)
++{
++ const struct mgmt_msg_session_req *msg = _msg;
++ struct mgmt_fe_session_ctx *session;
++ bool scok = adapter->conn->is_short_circuit;
++ uint64_t client_id;
++
++ _dbg("Got session-req is create %u req-id %Lu for refer-id %Lu notify-fmt %u from '%s'",
++ msg->refer_id == 0, msg->req_id, msg->refer_id, msg->notify_format, adapter->name);
++
++ /*
++ * It's important that any error has its refer_id set to 0 for create
++ * case and set to the passed in msg->refer_id otherwise (the destroy
++ * case). For non-error return pass the session_id for create or destroy.
++ */
++
++ if (msg->refer_id) {
++ uint64_t session_id = msg->refer_id;
++
++ session = fe_session_lookup(session_id);
++ if (!session) {
++ fe_adapter_conn_send_error(adapter->conn, session_id, msg->req_id, scok,
++ EINVAL,
++ "No session to delete for session-id: %" PRIu64,
++ session_id);
++ return;
++ }
++ fe_adapter_send_session_reply(adapter, session_id, msg->req_id, false);
++ fe_session_cleanup(&session);
++ return;
++ }
++
++ client_id = msg->req_id;
++
++ /* Default notification format */
++ if (msg->notify_format && msg->notify_format > MGMT_MSG_FORMAT_LAST) {
++ fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL,
++ "Unrecognized notify format: %u", msg->notify_format);
++ return;
++ }
++
++ /* See if we have a client name to register */
++ if (msg_len > sizeof(*msg)) {
++ if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) {
++ fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL,
++ "Corrupt session-req msg from client-id: %Lu",
++ client_id);
++ return;
++ }
++ _dbg("Set client-name to '%s'", msg->client_name);
++ strlcpy(adapter->name, msg->client_name, sizeof(adapter->name));
+ }
++
++ session = fe_session_create(adapter, msg->notify_format ?: DEFAULT_NOTIFY_FORMAT,
++ client_id);
++ fe_adapter_send_session_reply(adapter, session->session_id, msg->req_id, true);
+ }
+
+-/**
+- * Handle a native encoded message from the FE client.
++/*
++ * Handle a message from the FE client.
+ */
+-static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
+- struct mgmt_msg_header *msg,
+- size_t msg_len)
++static void fe_adapter_process_msg(uint8_t version, uint8_t *data, size_t msg_len,
++ struct msg_conn *conn)
+ {
++ struct mgmt_fe_client_adapter *adapter = conn->user;
+ struct mgmt_fe_session_ctx *session;
+- size_t min_size = mgmt_msg_get_min_size(msg->code);
++ struct mgmt_msg_header *msg = (typeof(msg))data;
++ size_t min_size;
++
++ if (version != MGMT_MSG_VERSION_NATIVE) {
++ _log_err("Protobuf not supported for frontend messages (adapter: %s)",
++ adapter->name);
++ return;
++ }
++
++ if (msg_len < sizeof(*msg)) {
++ _log_err("native message to adapter %s too short %zu", adapter->name, msg_len);
++ return;
++ }
+
++ min_size = mgmt_msg_get_min_size(msg->code);
+ if (msg_len < min_size) {
+ if (!min_size)
+ _log_err("adapter %s: recv msg refer-id %" PRIu64 " unknown message type %u",
+@@ -1374,6 +1499,10 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
+ return;
+ }
+
++ /*
++ * Handle new session requests up front.
++ */
++
+ if (msg->code == MGMT_MSG_CODE_SESSION_REQ) {
+ _dbg("adapter %s: session-id %Lu received SESSION_REQ message", adapter->name,
+ msg->refer_id);
+@@ -1381,7 +1510,11 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
+ return;
+ }
+
+- session = mgmt_session_id2ctx(msg->refer_id);
++ /*
++ * Get session and handle all other message types.
++ */
++
++ session = fe_session_lookup(msg->refer_id);
+ if (!session) {
+ _log_err("adapter %s: recv msg unknown session-id %" PRIu64, adapter->name,
+ msg->refer_id);
+@@ -1393,32 +1526,32 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
+ case MGMT_MSG_CODE_COMMIT:
+ _dbg("adapter %s: session-id %Lu received COMMIT message", adapter->name,
+ msg->refer_id);
+- fe_adapter_handle_commit(session, msg, msg_len);
++ fe_session_handle_commit(session, msg, msg_len);
+ break;
+ case MGMT_MSG_CODE_EDIT:
+ _dbg("adapter %s: session-id %" PRIu64 " received EDIT message", adapter->name,
+ msg->refer_id);
+- fe_adapter_handle_edit(session, msg, msg_len);
++ fe_session_handle_edit(session, msg, msg_len);
+ break;
+ case MGMT_MSG_CODE_LOCK:
+ _dbg("adapter %s: session-id %Lu received LOCK message", adapter->name,
+ msg->refer_id);
+- fe_adapter_handle_lock(session, msg, msg_len);
++ fe_session_handle_lock(session, msg, msg_len);
+ break;
+ case MGMT_MSG_CODE_NOTIFY_SELECT:
+ _dbg("adapter %s: session-id %" PRIu64 " received NOTIFY_SELECT message",
+ adapter->name, msg->refer_id);
+- fe_adapter_handle_notify_select(session, msg, msg_len);
++ fe_session_handle_notify_select(session, msg, msg_len);
+ break;
+ case MGMT_MSG_CODE_GET_DATA:
+ _dbg("adapter %s: session-id %" PRIu64 " received GET_DATA message", adapter->name,
+ msg->refer_id);
+- fe_adapter_handle_get_data(session, msg, msg_len);
++ fe_session_handle_get_data(session, msg, msg_len);
+ break;
+ case MGMT_MSG_CODE_RPC:
+ _dbg("adapter %s: session-id %" PRIu64 " received RPC message", adapter->name,
+ msg->refer_id);
+- fe_adapter_handle_rpc(session, msg, msg_len);
++ fe_session_handle_rpc(session, msg, msg_len);
+ break;
+ default:
+ _log_err("unknown native message session-id %" PRIu64 " req-id %" PRIu64
+@@ -1428,25 +1561,9 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
+ }
+ }
+
+-
+-static void mgmt_fe_adapter_process_msg(uint8_t version, uint8_t *data,
+- size_t len, struct msg_conn *conn)
+-{
+- struct mgmt_fe_client_adapter *adapter = conn->user;
+-
+- if (version == MGMT_MSG_VERSION_NATIVE) {
+- struct mgmt_msg_header *msg = (typeof(msg))data;
+-
+- if (len >= sizeof(*msg))
+- fe_adapter_handle_native_msg(adapter, msg, len);
+- else
+- _log_err("native message to adapter %s too short %zu", adapter->name, len);
+- return;
+- }
+-
+- _log_err("Protobuf not supported for frontend messages (adapter: %s)", adapter->name);
+-}
+-
++/* ============================= */
++/* Async Notification Processing */
++/* ============================= */
+
+ static struct mgmt_msg_notify_data *assure_notify_msg_cache(const struct mgmt_msg_notify_data *msg,
+ size_t msglen, struct lyd_node **tree,
+@@ -1559,7 +1676,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
+ * notifications it first gets a dump of all the subscribed state.
+ */
+ if (msg->refer_id != MGMTD_SESSION_ID_NONE) {
+- session = mgmt_session_id2ctx(msg->refer_id);
++ session = fe_session_lookup(msg->refer_id);
+ if (!session || !session->notify_xpaths) {
+ _dbg("No session listening for notify 'get' data: %Lu", msg->refer_id);
+ return;
+@@ -1567,7 +1684,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
+
+ send_msg = assure_notify_msg_cache(msg, msglen, &tree, session->notify_format,
+ cache);
+- (void)fe_adapter_send_native_msg(session->adapter, send_msg, msglen, false);
++ (void)fe_adapter_send_msg(session->adapter, send_msg, msglen, false);
+ goto done;
+ }
+
+@@ -1602,7 +1719,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
+ send_msg = assure_notify_msg_cache(msg, msglen, &tree, sessions[i]->notify_format,
+ cache);
+ send_msg->refer_id = sessions[i]->session_id;
+- (void)fe_adapter_send_native_msg(sessions[i]->adapter, send_msg, msglen, false);
++ (void)fe_adapter_send_msg(sessions[i]->adapter, send_msg, msglen, false);
+ }
+ darr_free(sessions);
+
+@@ -1612,14 +1729,14 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
+ * scheme).
+ */
+ if (CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF)) {
+- FOREACH_ADAPTER_IN_LIST (adapter) {
+- FOREACH_SESSION_IN_LIST (adapter, session) {
++ LIST_FOREACH (adapter, &fe_adapters, link) {
++ LIST_FOREACH (session, &adapter->sessions, link) {
+ if (session->notify_xpaths)
+ continue;
+ send_msg = assure_notify_msg_cache(msg, msglen, &tree,
+ session->notify_format, cache);
+ send_msg->refer_id = session->session_id;
+- (void)fe_adapter_send_native_msg(adapter, send_msg, msglen, false);
++ (void)fe_adapter_send_msg(adapter, send_msg, msglen, false);
+ }
+ }
+ }
+@@ -1630,275 +1747,37 @@ done:
+ cleanup_notify_msg_cache(msg, &tree, cache);
+ }
+
+-void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter)
+-{
+- adapter->refcount++;
+-}
+-
+-void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter)
+-{
+- struct mgmt_fe_client_adapter *a = *adapter;
+-
+- assert(a && a->refcount);
+-
+- if (!--a->refcount) {
+- mgmt_fe_adapters_del(&mgmt_fe_adapters, a);
+- msg_server_conn_delete(a->conn);
+- XFREE(MTYPE_MGMTD_FE_ADPATER, a);
+- }
+- *adapter = NULL;
+-}
+-
+-/*
+- * Initialize the FE adapter module
+- */
+-void mgmt_fe_adapter_init(struct event_loop *tm)
+-{
+- char server_path[MAXPATHLEN];
+-
+- assert(!mgmt_loop);
+- mgmt_loop = tm;
+-
+- mgmt_fe_adapters_init(&mgmt_fe_adapters);
+-
+- assert(!mgmt_fe_sessions);
+- mgmt_fe_sessions =
+- hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp,
+- "MGMT Frontend Sessions");
+-
+- ns_string_init(&mgmt_fe_ns_strings);
+-
+- snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME);
+-
+- if (msg_server_init(&mgmt_fe_server, server_path, tm,
+- mgmt_fe_create_adapter, "frontend", &mgmt_debug_fe)) {
+- zlog_err("cannot initialize frontend server");
+- exit(1);
+- }
+-}
+-
+-static FRR_NORETURN void mgmt_fe_abort_if_session(void *data)
+-{
+- struct mgmt_fe_session_ctx *session = data;
+-
+- _log_err("found orphaned session id %" PRIu64 " client id %" PRIu64 " adapter %s",
+- session->session_id, session->client_id,
+- session->adapter ? session->adapter->name : "NULL");
+- abort();
+-}
+-
+-/*
+- * Destroy the FE adapter module
+- */
+-void mgmt_fe_adapter_destroy(void)
+-{
+- struct mgmt_fe_client_adapter *adapter;
+-
+- msg_server_cleanup(&mgmt_fe_server);
+-
+-
+- /* Deleting the adapters will delete all the sessions */
+- FOREACH_ADAPTER_IN_LIST (adapter)
+- mgmt_fe_adapter_delete(adapter);
+-
+- mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings);
+-
+- hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session);
+-}
+-
+-/*
+- * The server accepted a new connection
+- */
+-struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from)
+-{
+- struct mgmt_fe_client_adapter *adapter = NULL;
+-
+- adapter = mgmt_fe_find_adapter_by_fd(conn_fd);
+- if (!adapter) {
+- adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter));
+- snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d",
+- conn_fd);
+-
+- mgmt_fe_sessions_init(&adapter->fe_sessions);
+- mgmt_fe_adapter_lock(adapter);
+- mgmt_fe_adapters_add_tail(&mgmt_fe_adapters, adapter);
+-
+- adapter->conn = msg_server_conn_create(
+- mgmt_loop, conn_fd, mgmt_fe_adapter_notify_disconnect,
+- mgmt_fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC,
+- MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN,
+- adapter, "FE-adapter");
+-
+- adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe,
+- DEBUG_MODE_ALL);
+-
+- adapter->cmt_stats.min_tm = ULONG_MAX;
+- _dbg("Added new MGMTD Frontend adapter '%s'", adapter->name);
+- }
+- return adapter->conn;
+-}
+-
+-int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, enum mgmt_ds_id src_ds_id,
+- enum mgmt_ds_id dst_ds_id, uint64_t req_id, bool validate_only,
+- bool unlock, enum mgmt_result result, const char *error_if_any)
+-{
+- struct mgmt_fe_session_ctx *session;
+- uint8_t action;
+-
+- session = mgmt_session_id2ctx(session_id);
+- if (!session || session->cfg_txn_id != txn_id)
+- return -1;
+-
+- if (validate_only)
+- action = MGMT_MSG_COMMIT_VALIDATE;
+- else
+- action = MGMT_MSG_COMMIT_APPLY;
+-
+- /* Cleanup the CONFIG transaction associated with this session. */
+- if (session->cfg_txn_id &&
+- ((result == MGMTD_SUCCESS && !validate_only) || (result == MGMTD_NO_CFG_CHANGES)))
+- mgmt_fe_session_register_event(session, MGMTD_FE_SESSION_CFG_TXN_CLNUP);
+-
+- if (mm->perf_stats_en)
+- gettimeofday(&session->adapter->cmt_stats.last_end, NULL);
+- mgmt_fe_session_compute_commit_timers(&session->adapter->cmt_stats);
+-
+- if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES)
+- return fe_adapter_send_error(
+- session, req_id, false, EINVAL /* convert result */,
+- "commit failed session-id %Lu on %s req-id %Lu source-ds: %s target-ds: %s validate-only: %u: reason: '%s'",
+- session->session_id, session->adapter->name, req_id,
+- mgmt_ds_id2name(src_ds_id), mgmt_ds_id2name(dst_ds_id), validate_only,
+- error_if_any ?: "");
+- fe_adapter_send_commit_reply(session, req_id, src_ds_id, dst_ds_id, action, unlock);
+- return 0;
+-}
+-
+-int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
+- uint64_t req_id, LYD_FORMAT result_type,
+- uint32_t wd_options,
+- const struct lyd_node *tree,
+- int partial_error, bool short_circuit_ok)
+-{
+- struct mgmt_fe_session_ctx *session;
+- int ret;
+-
+- session = mgmt_session_id2ctx(session_id);
+- if (!session || session->txn_id != txn_id)
+- return -1;
+-
+- ret = fe_adapter_send_tree_data(session, req_id, short_circuit_ok,
+- result_type, wd_options, tree,
+- partial_error);
+-
+- mgmt_destroy_txn(&session->txn_id);
+-
+- return ret;
+-}
+-
+-int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id,
+- uint64_t req_id, LYD_FORMAT result_type,
+- const struct lyd_node *result)
+-{
+- struct mgmt_fe_session_ctx *session;
+- int ret;
+-
+- session = mgmt_session_id2ctx(session_id);
+- if (!session || session->txn_id != txn_id)
+- return -1;
+
+- ret = fe_adapter_send_rpc_reply(session, req_id, result_type, result);
++/* =========================== */
++/* Frontend VTY and Statistics */
++/* =========================== */
+
+- mgmt_destroy_txn(&session->txn_id);
+-
+- return ret;
+-}
+-
+-int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id,
+- uint64_t req_id, bool unlock, bool commit,
+- bool created, const char *xpath,
+- int16_t error, const char *errstr)
++static void fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats)
+ {
+- struct mgmt_fe_session_ctx *session;
+- enum mgmt_ds_id ds_id, rds_id;
+- struct mgmt_ds_ctx *ds_ctx, *rds_ctx;
+- int ret;
+-
+- session = mgmt_session_id2ctx(session_id);
+- if (!session || session->cfg_txn_id != txn_id)
+- return -1;
+-
+- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && commit)
+- mgmt_fe_session_register_event(session,
+- MGMTD_FE_SESSION_CFG_TXN_CLNUP);
+-
+- if (unlock) {
+- ds_id = MGMTD_DS_CANDIDATE;
+- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id);
+- assert(ds_ctx);
+-
+- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
+-
+- if (commit) {
+- rds_id = MGMTD_DS_RUNNING;
+- rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id);
+- assert(rds_ctx);
+-
+- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, session);
+- }
++ cmt_stats->last_exec_tm = timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start);
++ if (cmt_stats->last_exec_tm > cmt_stats->max_tm) {
++ cmt_stats->max_tm = cmt_stats->last_exec_tm;
++ cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt;
+ }
+
+- if (error != 0 && error != -EALREADY)
+- ret = fe_adapter_send_error(session, req_id, false, error, "%s",
+- errstr);
+- else
+- ret = fe_adapter_send_edit_reply(session, req_id, created,
+- !error, xpath, errstr);
+-
+- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && !commit)
+- mgmt_destroy_txn(&session->cfg_txn_id);
+-
+- return ret;
+-}
+-
+-/**
+- * Send an error back to the FE client and cleanup any in-progress txn.
+- */
+-int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id,
+- bool short_circuit_ok, int16_t error,
+- const char *errstr)
+-{
+- struct mgmt_fe_session_ctx *session;
+- int ret;
+-
+- session = fe_adapter_session_by_txn_id(txn_id);
+- if (!session) {
+- _log_err("failed sending error for txn-id %" PRIu64 " session not found", txn_id);
+- return -ENOENT;
++ if (cmt_stats->last_exec_tm < cmt_stats->min_tm) {
++ cmt_stats->min_tm = cmt_stats->last_exec_tm;
++ cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt;
+ }
+-
+- ret = fe_adapter_send_error(session, req_id, false, error, "%s", errstr);
+-
+- mgmt_destroy_txn(&session->txn_id);
+-
+- return ret;
+ }
+
+-struct mgmt_commit_stats *
+-mgmt_fe_get_session_commit_stats(uint64_t session_id)
++struct mgmt_commit_stats *mgmt_fe_get_session_commit_stats(uint64_t session_id)
+ {
+ struct mgmt_fe_session_ctx *session;
+
+- session = mgmt_session_id2ctx(session_id);
++ session = fe_session_lookup(session_id);
+ if (!session || !session->adapter)
+ return NULL;
+
+ return &session->adapter->cmt_stats;
+ }
+
+-static void
+-mgmt_fe_adapter_cmt_stats_write(struct vty *vty,
+- struct mgmt_fe_client_adapter *adapter)
++static void _cmt_stats_write(struct vty *vty, struct mgmt_fe_client_adapter *adapter)
+ {
+ char buf[MGMT_LONG_TIME_MAX_LEN];
+
+@@ -1968,17 +1847,20 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail)
+ struct mgmt_fe_session_ctx *session;
+ enum mgmt_ds_id ds_id;
+ bool locked = false;
++ uint acount = 0;
++ uint scount;
+
+ vty_out(vty, "MGMTD Frontend Adpaters\n");
+
+- FOREACH_ADAPTER_IN_LIST (adapter) {
++ LIST_FOREACH (adapter, &fe_adapters, link) {
+ vty_out(vty, " Client: \t\t\t\t%s\n", adapter->name);
+ vty_out(vty, " Conn-FD: \t\t\t\t%d\n", adapter->conn->fd);
+ if (detail) {
+- mgmt_fe_adapter_cmt_stats_write(vty, adapter);
++ _cmt_stats_write(vty, adapter);
+ }
++ scount = 0;
+ vty_out(vty, " Sessions\n");
+- FOREACH_SESSION_IN_LIST (adapter, session) {
++ LIST_FOREACH (session, &adapter->sessions, link) {
+ vty_out(vty, " Session: \t\t\t\t%p\n", session);
+ vty_out(vty, " Client-Id: \t\t\t%" PRIu64 "\n",
+ session->client_id);
+@@ -1994,9 +1876,9 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail)
+ }
+ if (!locked)
+ vty_out(vty, " None\n");
++ scount++;
+ }
+- vty_out(vty, " Total-Sessions: \t\t\t%d\n",
+- (int)mgmt_fe_sessions_count(&adapter->fe_sessions));
++ vty_out(vty, " Total-Sessions: \t\t\t%u\n", scount);
+ vty_out(vty, " Msg-Recvd: \t\t\t\t%" PRIu64 "\n",
+ adapter->conn->mstate.nrxm);
+ vty_out(vty, " Bytes-Recvd: \t\t\t%" PRIu64 "\n",
+@@ -2006,8 +1888,7 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail)
+ vty_out(vty, " Bytes-Sent: \t\t\t%" PRIu64 "\n",
+ adapter->conn->mstate.ntxb);
+ }
+- vty_out(vty, " Total: %d\n",
+- (int)mgmt_fe_adapters_count(&mgmt_fe_adapters));
++ vty_out(vty, " Total: %u\n", acount);
+ }
+
+ void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config)
+@@ -2018,12 +1899,127 @@ void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config)
+ void mgmt_fe_adapter_reset_perf_stats(struct vty *vty)
+ {
+ struct mgmt_fe_client_adapter *adapter;
+- struct mgmt_fe_session_ctx *session;
+
+- FOREACH_ADAPTER_IN_LIST (adapter) {
+- FOREACH_SESSION_IN_LIST (adapter, session) {
+- memset(&adapter->cmt_stats, 0,
+- sizeof(adapter->cmt_stats));
+- }
++ LIST_FOREACH (adapter, &fe_adapters, link)
++ memset(&adapter->cmt_stats, 0, sizeof(adapter->cmt_stats));
++}
++
++/* =================== */
++/* Frontend Management */
++/* =================== */
++
++void mgmt_fe_adapter_toggle_client_debug(bool set)
++{
++ struct mgmt_fe_client_adapter *adapter;
++
++ LIST_FOREACH (adapter, &fe_adapters, link)
++ adapter->conn->debug = set;
++}
++
++static struct mgmt_fe_client_adapter *fe_adapter_lookup_by_fd(int conn_fd)
++{
++ struct mgmt_fe_client_adapter *adapter;
++
++ LIST_FOREACH (adapter, &fe_adapters, link)
++ if (adapter->conn->fd == conn_fd)
++ return adapter;
++ return NULL;
++}
++
++static void fe_adapter_delete(struct mgmt_fe_client_adapter *adapter)
++{
++ struct mgmt_fe_session_ctx *session, *next;
++
++ _dbg("Deleting client adapter '%s'", adapter->name);
++
++ /* TODO: notify about client disconnect for appropriate cleanup */
++ LIST_FOREACH_SAFE (session, &adapter->sessions, link, next)
++ fe_session_cleanup(&session);
++ LIST_REMOVE(adapter, link);
++ msg_server_conn_delete(adapter->conn);
++ XFREE(MTYPE_MGMTD_FE_ADPATER, adapter);
++}
++
++static int fe_adapter_notify_disconnect(struct msg_conn *conn)
++{
++ struct mgmt_fe_client_adapter *adapter = conn->user;
++
++ _dbg("notify disconnect for client adapter '%s'", adapter->name);
++
++ fe_adapter_delete(adapter);
++
++ return 0;
++}
++
++/*
++ * The server accepted a new connection
++ */
++static struct msg_conn *fe_adapter_create(int conn_fd, union sockunion *from)
++{
++ struct mgmt_fe_client_adapter *adapter = NULL;
++
++ adapter = fe_adapter_lookup_by_fd(conn_fd);
++ if (!adapter) {
++ adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter));
++ snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd);
++
++ LIST_INSERT_HEAD(&fe_adapters, adapter, link);
++
++ adapter->conn =
++ msg_server_conn_create(mgmt_loop, conn_fd, fe_adapter_notify_disconnect,
++ fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC,
++ MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN,
++ adapter, "FE-adapter");
++
++ adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL);
++
++ adapter->cmt_stats.min_tm = ULONG_MAX;
++ _dbg("Added new MGMTD Frontend adapter '%s'", adapter->name);
++ }
++ return adapter->conn;
++}
++
++
++/*
++ * Initialize the FE adapter module
++ */
++void mgmt_fe_adapter_init(struct event_loop *tm)
++{
++ char server_path[MAXPATHLEN];
++
++ assert(!mgmt_loop);
++ mgmt_loop = tm;
++
++ assert(!mgmt_fe_sessions);
++ mgmt_fe_sessions = hash_create(fe_session_hash_key, fe_session_hash_cmp,
++ "MGMT Frontend Sessions");
++
++ ns_string_init(&mgmt_fe_ns_strings);
++
++ snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME);
++
++ if (msg_server_init(&mgmt_fe_server, server_path, tm, fe_adapter_create, "frontend",
++ &mgmt_debug_fe)) {
++ zlog_err("cannot initialize frontend server");
++ exit(1);
+ }
+ }
++
++/*
++ * Destroy the FE adapter module
++ */
++void mgmt_fe_adapter_destroy(void)
++{
++ struct mgmt_fe_client_adapter *adapter, *next;
++
++ msg_server_cleanup(&mgmt_fe_server);
++
++
++ /* Deleting the adapters will delete all the sessions */
++ LIST_FOREACH_SAFE (adapter, &fe_adapters, link, next)
++ fe_adapter_delete(adapter);
++
++ mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings);
++
++ hash_free(mgmt_fe_sessions);
++}
+diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h
+index c0ba95c5e7..bd3b4bad91 100644
+--- a/mgmtd/mgmt_fe_adapter.h
++++ b/mgmtd/mgmt_fe_adapter.h
+@@ -14,9 +14,6 @@
+ #include "mgmt_msg.h"
+ #include "mgmt_defines.h"
+
+-struct mgmt_fe_client_adapter;
+-struct mgmt_master;
+-
+ struct mgmt_commit_stats {
+ struct timeval last_start;
+ struct timeval prep_cfg_start;
+@@ -36,44 +33,24 @@ struct mgmt_commit_stats {
+ unsigned long commit_cnt;
+ };
+
+-PREDECL_LIST(mgmt_fe_sessions);
+-
+-PREDECL_LIST(mgmt_fe_adapters);
+-
+-struct mgmt_fe_client_adapter {
+- struct msg_conn *conn;
+- char name[MGMTD_CLIENT_NAME_MAX_LEN];
+-
+- /* List of sessions created and being maintained for this client. */
+- struct mgmt_fe_sessions_head fe_sessions;
+-
+- int refcount;
+- struct mgmt_commit_stats cmt_stats;
+-
+- struct mgmt_fe_adapters_item list_linkage;
++struct mgmt_edit_req {
++ char xpath_created[XPATH_MAXLEN];
++ struct nb_config *nb_backup;
++ bool created;
++ bool unlock_candidate;
++ bool unlock_running;
+ };
+
+-DECLARE_LIST(mgmt_fe_adapters, struct mgmt_fe_client_adapter, list_linkage);
+-
+ /* Initialise frontend adapter module */
+ extern void mgmt_fe_adapter_init(struct event_loop *tm);
+
+ /* Destroy frontend adapter module */
+ extern void mgmt_fe_adapter_destroy(void);
+
+-/* Acquire lock for frontend adapter */
+-extern void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter);
+-
+-/* Remove lock from frontend adapter */
+-extern void
+-mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter);
+-
+-/* Create frontend adapter */
+-extern struct msg_conn *mgmt_fe_create_adapter(int conn_fd,
+- union sockunion *su);
+-
+ /*
+ * Send commit-config reply to the frontend client.
++ *
++ * This also cleans up and frees the transaction.
+ */
+ extern int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id,
+ enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dst_ds_id,
+@@ -94,16 +71,11 @@ extern int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id,
+ * tree: the results.
+ * partial_error: if there were errors while gather results.
+ * short_circuit_ok: True if OK to short-circuit the call.
+- *
+- * Return:
+- * the return value from the underlying send function.
+- *
+ */
+-extern int
+-mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
+- uint64_t req_id, LYD_FORMAT result_type,
+- uint32_t wd_options, const struct lyd_node *tree,
+- int partial_error, bool short_circuit_ok);
++extern void mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id,
++ LYD_FORMAT result_type, uint32_t wd_options,
++ const struct lyd_node *tree, int partial_error,
++ bool short_circuit_ok);
+
+ /**
+ * Send RPC reply back to client.
+@@ -116,14 +88,9 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
+ * req_id: the req id for the rpc message
+ * result_type: the format of the result data.
+ * result: the results.
+- *
+- * Return:
+- * the return value from the underlying send function.
+ */
+-extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id,
+- uint64_t req_id,
+- LYD_FORMAT result_type,
+- const struct lyd_node *result);
++extern void mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id,
++ LYD_FORMAT result_type, const struct lyd_node *result);
+
+ /**
+ * Send edit reply back to client. If error is not 0, a native error is sent.
+@@ -136,16 +103,20 @@ extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id,
+ * req_id: the req id for the edit message
+ * unlock: implicit-lock flag was set in the request
+ * commit: implicit-commit flag was set in the request
+- * created: true if the node was just created
+- * xpath: the xpath of the data node that was created/updated
+- * error: >0 LY_ERR, < 0 -errno
++ * edit: the edit request info created when processing the edit
++ * error: mgmt result code
+ * errstr: the error string, if error is non-zero
+ */
+-extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id,
+- uint64_t req_id, bool unlock,
+- bool commit, bool created,
+- const char *xpath, int16_t error,
+- const char *errstr);
++extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id,
++ bool unlock, bool commit, struct mgmt_edit_req **edit,
++ enum mgmt_result error, const char *errstr);
++
++/**
++ * mgmt_fe_adapter_send_notify() - notify FE clients of a notification.
++ * @msg: the notify message from the backend client.
++ * @msglen: the length of the notify message.
++ */
++extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen);
+
+ /**
+ * Send an error back to the FE client using native messaging.
+@@ -163,10 +134,8 @@ extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id,
+ * the return value from the underlying send function.
+ *
+ */
+-extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id,
+- bool short_circuit_ok, int16_t error,
+- const char *errstr);
+-
++extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, bool short_circuit_ok,
++ int16_t error, const char *errstr);
+
+ /**
+ * mgmt_fe_get_all_selectors() - Get all selectors for all frontend adapters.
+diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c
+index 934748b1f2..91fae105ae 100644
+--- a/mgmtd/mgmt_history.c
++++ b/mgmtd/mgmt_history.c
+@@ -216,7 +216,7 @@ static int mgmt_history_rollback_to_cmt(struct vty *vty,
+
+ ret = mgmt_ds_lock(dst_ds_ctx, vty->mgmt_session_id);
+ if (ret != 0) {
+- mgmt_ds_unlock(src_ds_ctx);
++ mgmt_ds_unlock(src_ds_ctx, vty->mgmt_session_id);
+ vty_out(vty,
+ "Failed to lock the DS %u for rollback Reason: %s!\n",
+ MGMTD_DS_RUNNING, strerror(ret));
+@@ -260,16 +260,20 @@ static int mgmt_history_rollback_to_cmt(struct vty *vty,
+ return 0;
+
+ failed_unlock:
+- mgmt_ds_unlock(src_ds_ctx);
+- mgmt_ds_unlock(dst_ds_ctx);
++ mgmt_ds_unlock(src_ds_ctx, vty->mgmt_session_id);
++ mgmt_ds_unlock(dst_ds_ctx, vty->mgmt_session_id);
+ return ret;
+ }
+
+ void mgmt_history_rollback_complete(bool success)
+ {
+- vty_mgmt_resume_response(rollback_vty,
+- success ? CMD_SUCCESS
+- : CMD_WARNING_CONFIG_FAILED);
++ struct mgmt_ds_ctx *src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE);
++ struct mgmt_ds_ctx *dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
++
++ mgmt_ds_unlock(src_ds_ctx, rollback_vty->mgmt_session_id);
++ mgmt_ds_unlock(dst_ds_ctx, rollback_vty->mgmt_session_id);
++
++ vty_mgmt_resume_response(rollback_vty, success ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED);
+ rollback_vty = NULL;
+ }
+
+diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c
+index 0858fede2a..720286c9e0 100644
+--- a/mgmtd/mgmt_main.c
++++ b/mgmtd/mgmt_main.c
+@@ -114,6 +114,8 @@ static FRR_NORETURN void mgmt_exit(int status)
+ /* it only makes sense for this to be called on a clean exit */
+ assert(status == 0);
+
++ vty_mgmt_terminate();
++
+ frr_early_fini();
+
+ /* stop pthreads (if any) */
+diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c
+index 6d07212d99..0183e4b362 100644
+--- a/mgmtd/mgmt_memory.c
++++ b/mgmtd/mgmt_memory.c
+@@ -26,7 +26,4 @@ DEFINE_MTYPE(MGMTD, MGMTD_FE_ADPATER, "frontend adapter");
+ DEFINE_MTYPE(MGMTD, MGMTD_FE_SESSION, "frontend session");
+ DEFINE_MTYPE(MGMTD, MGMTD_TXN, "txn");
+ DEFINE_MTYPE(MGMTD, MGMTD_TXN_REQ, "txn request");
+-DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ, "txn commit-config requests");
+-DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETTREE_REQ, "txn get-tree requests");
+-DEFINE_MTYPE(MGMTD, MGMTD_TXN_RPC_REQ, "txn rpc requests");
+ DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "commit info");
+diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h
+index 7e904e03ab..36156f7eb7 100644
+--- a/mgmtd/mgmt_memory.h
++++ b/mgmtd/mgmt_memory.h
+@@ -20,8 +20,5 @@ DECLARE_MTYPE(MGMTD_FE_ADPATER);
+ DECLARE_MTYPE(MGMTD_FE_SESSION);
+ DECLARE_MTYPE(MGMTD_TXN);
+ DECLARE_MTYPE(MGMTD_TXN_REQ);
+-DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ);
+-DECLARE_MTYPE(MGMTD_TXN_GETTREE_REQ);
+-DECLARE_MTYPE(MGMTD_TXN_RPC_REQ);
+ DECLARE_MTYPE(MGMTD_CMT_INFO);
+ #endif /* _FRR_MGMTD_MEMORY_H */
+diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
+index fb98c810af..386b14c837 100644
+--- a/mgmtd/mgmt_txn.c
++++ b/mgmtd/mgmt_txn.c
+@@ -4,6 +4,7 @@
+ *
+ * Copyright (C) 2021 Vmware, Inc.
+ * Pushpasis Sarkar <spushpasis@vmware.com>
++ * Copyright (c) 2025, LabN Consulting, L.L.C.
+ */
+
+ #include <zebra.h>
+@@ -14,119 +15,17 @@
+ #include "mgmt_msg.h"
+ #include "mgmt_msg_native.h"
+ #include "mgmtd/mgmt.h"
+-#include "mgmtd/mgmt_memory.h"
+-#include "mgmtd/mgmt_txn.h"
+-#include "mgmtd/mgmt_be_adapter.h"
++#include "mgmtd/mgmt_txn_priv.h"
+
+-#define _dbg(fmt, ...) DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__)
+-#define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+-
+-#define MGMTD_TXN_LOCK(txn) mgmt_txn_lock(txn, __FILE__, __LINE__)
+-#define MGMTD_TXN_UNLOCK(txn, in_hash_free) mgmt_txn_unlock(txn, in_hash_free, __FILE__, __LINE__)
+-
+-enum mgmt_txn_req_type {
+- MGMTD_TXN_PROC_COMMITCFG = 1,
+- MGMTD_TXN_PROC_GETTREE,
+- MGMTD_TXN_PROC_RPC,
+-};
+-
+-enum mgmt_txn_frr_event {
+- MGMTD_TXN_EVENT_COMMITCFG = 1,
+- MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT,
+-};
+-
+-PREDECL_LIST(mgmt_txn_reqs);
+-
+-enum mgmt_commit_phase {
+- MGMTD_COMMIT_PHASE_PREPARE_CFG = 0,
+- MGMTD_COMMIT_PHASE_TXN_CREATE,
+- MGMTD_COMMIT_PHASE_APPLY_CFG,
+- MGMTD_COMMIT_PHASE_TXN_DELETE,
+- MGMTD_COMMIT_PHASE_MAX
+-};
+-
+-static inline const char *mgmt_commit_phase2str(enum mgmt_commit_phase cmt_phase)
+-{
+- switch (cmt_phase) {
+- case MGMTD_COMMIT_PHASE_PREPARE_CFG:
+- return "PREP-CFG";
+- case MGMTD_COMMIT_PHASE_TXN_CREATE:
+- return "CREATE-TXN";
+- case MGMTD_COMMIT_PHASE_APPLY_CFG:
+- return "APPLY-CFG";
+- case MGMTD_COMMIT_PHASE_TXN_DELETE:
+- return "DELETE-TXN";
+- case MGMTD_COMMIT_PHASE_MAX:
+- return "Invalid/Unknown";
+- }
+-
+- return "Invalid/Unknown";
+-}
+-
+-struct mgmt_edit_req {
+- char xpath_created[XPATH_MAXLEN];
+- bool created;
+- bool unlock;
+-};
+-
+-struct mgmt_commit_cfg_req {
+- enum mgmt_ds_id src_ds_id;
+- struct mgmt_ds_ctx *src_ds_ctx;
+- enum mgmt_ds_id dst_ds_id;
+- struct mgmt_ds_ctx *dst_ds_ctx;
+- uint32_t nb_txn_id;
+- uint8_t validate_only : 1;
+- uint8_t abort : 1;
+- uint8_t implicit : 1;
+- uint8_t rollback : 1;
+- uint8_t init : 1;
+- uint8_t unlock : 1;
+-
+- /* Track commit phases */
+- enum mgmt_commit_phase phase;
+-
+- enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX];
+-
+- /*
+- * Additional information when the commit is triggered by native edit
+- * request.
+- */
+- struct mgmt_edit_req *edit;
+-
+- /*
+- * Set of config changes to commit. This is used only
+- * when changes are NOT to be determined by comparing
+- * candidate and running DSs. This is typically used
+- * for downloading all relevant configs for a new backend
+- * client that has recently come up and connected with
+- * MGMTD.
+- */
+- struct nb_config_cbs *cfg_chgs;
+-
+- /* Used for holding changes mgmtd itself is interested in */
+- struct nb_transaction *mgmtd_nb_txn;
+-
+- /*
+- * Details on all the Backend Clients associated with
+- * this commit.
+- */
+- uint64_t clients;
+-
+- /*
+- * Config messages to send to the backends, and their action strings to
+- * add to the message just prior to sending.
+- */
+- struct mgmt_msg_cfg_req *cfg_msgs[MGMTD_BE_CLIENT_ID_MAX];
+- char *cfg_actions[MGMTD_BE_CLIENT_ID_MAX];
+- uint64_t num_cfg_data[MGMTD_BE_CLIENT_ID_MAX];
+-
+- struct mgmt_commit_stats *cmt_stats;
+-};
++/* ================= */
++/* TXN Request Types */
++/* ================= */
+
+ struct txn_req_get_tree {
++ struct txn_req req;
+ char *xpath; /* xpath of tree to get */
+- uint64_t sent_clients; /* Bitmask of clients sent req to */
+- uint64_t recv_clients; /* Bitmask of clients recv reply from */
++ uint64_t clients; /* Bitmask of clients sent req to */
++ uint64_t clients_wait; /* Bitmask of clients waiting for reply from */
+ int32_t partial_error; /* an error while gather results */
+ uint8_t result_type; /* LYD_FORMAT for results */
+ uint8_t wd_options; /* LYD_PRINT_WD_* flags for results */
+@@ -134,807 +33,147 @@ struct txn_req_get_tree {
+ uint8_t simple_xpath; /* if xpath is simple */
+ struct lyd_node *client_results; /* result tree from clients */
+ };
++#define as_get_tree(txn_req) \
++ ({ \
++ assert((txn_req)->req_type == TXN_REQ_TYPE_GETTREE); \
++ (struct txn_req_get_tree *)(txn_req); \
++ })
+
+ struct txn_req_rpc {
++ struct txn_req req;
+ char *xpath; /* xpath of rpc/action to invoke */
+- uint64_t sent_clients; /* Bitmask of clients sent req to */
+- uint64_t recv_clients; /* Bitmask of clients recv reply from */
++ uint64_t clients; /* Bitmask of clients sent req to */
++ uint64_t clients_wait; /* Bitmask of clients waiting for reply from */
+ uint8_t result_type; /* LYD_FORMAT for results */
+- char *errstr; /* error string */
+ struct lyd_node *client_results; /* result tree from clients */
+ };
+-
+-struct mgmt_txn_req {
+- struct mgmt_txn_ctx *txn;
+- enum mgmt_txn_req_type req_type;
+- uint64_t req_id;
+- union {
+- /*
+- * XXX Make the sub-structure variants either allocated or
+- * embedded -- not both; embedding only the commit_cfg variant
+- * makes little sense since it is the largest of the variants
+- * though!
+- */
+- struct txn_req_get_tree *get_tree;
+- struct txn_req_rpc *rpc;
+- struct mgmt_commit_cfg_req commit_cfg;
+- } req;
+-
+- struct mgmt_txn_reqs_item list_linkage;
++#define as_rpc(txn_req) \
++ ({ \
++ assert((txn_req)->req_type == TXN_REQ_TYPE_RPC); \
++ (struct txn_req_rpc *)(txn_req); \
++ })
++
++const char *txn_req_names[] = {
++ [TXN_REQ_TYPE_COMMIT] = "COMMIT",
++ [TXN_REQ_TYPE_GETTREE] = "GET-TREE",
++ [TXN_REQ_TYPE_RPC] = "RPC",
+ };
+
+-DECLARE_LIST(mgmt_txn_reqs, struct mgmt_txn_req, list_linkage);
+-
+-#define FOREACH_TXN_REQ_IN_LIST(list, req) \
+- frr_each_safe (mgmt_txn_reqs, list, req)
+-
+-struct mgmt_txn_ctx {
+- uint64_t session_id; /* One transaction per client session */
+- uint64_t txn_id;
+- enum mgmt_txn_type type;
+-
+- /* struct mgmt_master *mm; */
+-
+- struct event *proc_comm_cfg;
+- struct event *proc_get_tree;
+- struct event *comm_cfg_timeout;
+- struct event *get_tree_timeout;
+- struct event *rpc_timeout;
+- struct event *clnup;
+-
+- /* List of backend adapters involved in this transaction */
+- /* XXX reap this */
+- struct mgmt_txn_badapters_head be_adapters;
+-
+- int refcount;
+-
+- struct mgmt_txns_item list_linkage;
+-
+- /* TODO: why do we need unique lists for each type of transaction since
+- * a transaction is of only 1 type?
+- */
+-
+- /*
+- * List of pending get-tree requests.
+- */
+- struct mgmt_txn_reqs_head get_tree_reqs;
+- /*
+- * List of pending rpc requests.
+- */
+- struct mgmt_txn_reqs_head rpc_reqs;
+- /*
+- * There will always be one commit-config allowed for a given
+- * transaction/session. No need to maintain lists for it.
+- */
+- struct mgmt_txn_req *commit_cfg_req;
+-};
++/* Global list of all transactions */
++TAILQ_HEAD(mgmt_txn_head, mgmt_txn) txn_txns = TAILQ_HEAD_INITIALIZER(txn_txns);
+
+-DECLARE_LIST(mgmt_txns, struct mgmt_txn_ctx, list_linkage);
++/* The single instance of config transaction allowed at any time */
++struct mgmt_txn *txn_config_txn;
+
+-#define FOREACH_TXN_IN_LIST(mm, txn) \
+- frr_each_safe (mgmt_txns, &(mm)->txn_list, (txn))
++/* Map of Transactions and its ID */
++struct hash *txn_id_tab;
++uint64_t txn_next_id;
+
+-static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
+- enum mgmt_result result,
+- const char *error_if_any);
++static void txn_incref(struct mgmt_txn *txn, const char *file, int line);
+
+-static inline const char *mgmt_txn_commit_phase_str(struct mgmt_txn_ctx *txn)
++/* ====================== */
++/* Txn Request Management */
++/* ====================== */
++
++static inline struct txn_req *txn_txn_req(struct mgmt_txn *txn, uint64_t req_id)
+ {
+- if (!txn->commit_cfg_req)
+- return "None";
++ struct txn_req *txn_req;
+
+- return mgmt_commit_phase2str(txn->commit_cfg_req->req.commit_cfg.phase);
++ TAILQ_FOREACH (txn_req, &txn->reqs, link)
++ if (txn_req->req_id == req_id)
++ return txn_req;
++ return NULL;
+ }
+
+-static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line);
+-static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, bool in_hash_free, const char *file,
+- int line);
+-static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn,
+- struct mgmt_be_client_adapter *adapter);
+-
+-static struct event_loop *mgmt_txn_tm;
+-static struct mgmt_master *mgmt_txn_mm;
+-
+-static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, enum mgmt_txn_frr_event event);
+-
+-static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn);
+-
+-static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, uint64_t req_id,
+- enum mgmt_txn_req_type req_type)
++struct txn_req *txn_req_alloc(struct mgmt_txn *txn, uint64_t req_id, enum txn_req_type req_type,
++ size_t size)
+ {
+- struct mgmt_txn_req *txn_req;
++ struct txn_req *txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, size);
+
+- txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_txn_req));
+- assert(txn_req);
+- txn_req->txn = txn;
+- txn_req->req_id = req_id;
+ txn_req->req_type = req_type;
++ txn_req->req_id = req_id;
+
+- switch (txn_req->req_type) {
+- case MGMTD_TXN_PROC_COMMITCFG:
+- txn->commit_cfg_req = txn_req;
+- _dbg("Added a new COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64
+- " session-id: %" PRIu64,
+- txn_req->req_id, txn->txn_id, txn->session_id);
+-
+- txn_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_PREPARE_CFG;
+- break;
+- case MGMTD_TXN_PROC_GETTREE:
+- txn_req->req.get_tree = XCALLOC(MTYPE_MGMTD_TXN_GETTREE_REQ,
+- sizeof(struct txn_req_get_tree));
+- mgmt_txn_reqs_add_tail(&txn->get_tree_reqs, txn_req);
+- _dbg("Added a new GETTREE req-id: %" PRIu64 " txn-id: %" PRIu64
+- " session-id: %" PRIu64,
+- txn_req->req_id, txn->txn_id, txn->session_id);
+- break;
+- case MGMTD_TXN_PROC_RPC:
+- txn_req->req.rpc = XCALLOC(MTYPE_MGMTD_TXN_RPC_REQ,
+- sizeof(struct txn_req_rpc));
+- assert(txn_req->req.rpc);
+- mgmt_txn_reqs_add_tail(&txn->rpc_reqs, txn_req);
+- _dbg("Added a new RPC req-id: %" PRIu64 " txn-id: %" PRIu64 " session-id: %" PRIu64,
+- txn_req->req_id, txn->txn_id, txn->session_id);
+- break;
+- }
++ txn_req->txn = txn;
++ TAILQ_INSERT_TAIL(&txn->reqs, txn_req, link);
++ TXN_INCREF(txn);
+
+- MGMTD_TXN_LOCK(txn);
++ _dbg("Added %s txn-req req-id: %Lu txn-id: %Lu session-id: %Lu", txn_req_names[req_type],
++ req_id, txn->txn_id, txn->session_id);
+
+ return txn_req;
+ }
+
+-static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req)
++static struct txn_req_get_tree *txn_req_get_tree_alloc(struct mgmt_txn *txn, uint64_t req_id)
+ {
+- struct mgmt_txn_reqs_head *req_list = NULL;
+- enum mgmt_be_client_id id;
+- struct mgmt_be_client_adapter *adapter;
+- struct mgmt_commit_cfg_req *ccreq;
+- bool cleanup;
+-
+- switch ((*txn_req)->req_type) {
+- case MGMTD_TXN_PROC_COMMITCFG:
+- _dbg("Deleting COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id,
+- (*txn_req)->txn->txn_id);
+-
+- ccreq = &(*txn_req)->req.commit_cfg;
+- cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE &&
+- ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE);
+-
+- XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit);
+-
+- if (ccreq->mgmtd_nb_txn) {
+- nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0);
+- ccreq->mgmtd_nb_txn = NULL;
+- }
+-
+- FOREACH_MGMTD_BE_CLIENT_ID (id) {
+- /*
+- * Send TXN_DELETE to cleanup state for this
+- * transaction on backend
+- */
+-
+- /*
+- * Delete any config messages and action data
+- */
+- mgmt_msg_native_free_msg(ccreq->cfg_msgs[id]);
+- darr_free(ccreq->cfg_actions[id]);
+-
+- /*
+- * If we were in the middle of the state machine then
+- * send a txn delete message
+- */
+- adapter = mgmt_be_get_adapter_by_id(id);
+- if (adapter && cleanup && IS_IDBIT_SET(ccreq->clients, id))
+- mgmt_txn_send_be_txn_delete((*txn_req)->txn,
+- adapter);
+- }
+- break;
+- case MGMTD_TXN_PROC_GETTREE:
+- _dbg("Deleting GETTREE req-id: %" PRIu64 " of txn-id: %" PRIu64, (*txn_req)->req_id,
+- (*txn_req)->txn->txn_id);
+- req_list = &(*txn_req)->txn->get_tree_reqs;
+- lyd_free_all((*txn_req)->req.get_tree->client_results);
+- XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.get_tree->xpath);
+- XFREE(MTYPE_MGMTD_TXN_GETTREE_REQ, (*txn_req)->req.get_tree);
+- break;
+- case MGMTD_TXN_PROC_RPC:
+- _dbg("Deleting RPC req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id,
+- (*txn_req)->txn->txn_id);
+- req_list = &(*txn_req)->txn->rpc_reqs;
+- lyd_free_all((*txn_req)->req.rpc->client_results);
+- XFREE(MTYPE_MGMTD_ERR, (*txn_req)->req.rpc->errstr);
+- XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.rpc->xpath);
+- XFREE(MTYPE_MGMTD_TXN_RPC_REQ, (*txn_req)->req.rpc);
+- break;
+- }
+-
+- if (req_list) {
+- mgmt_txn_reqs_del(req_list, *txn_req);
+- _dbg("Removed req-id: %" PRIu64 " from request-list (left:%zu)", (*txn_req)->req_id,
+- mgmt_txn_reqs_count(req_list));
+- }
+-
+- MGMTD_TXN_UNLOCK(&(*txn_req)->txn, false);
+- XFREE(MTYPE_MGMTD_TXN_REQ, (*txn_req));
+- *txn_req = NULL;
+-}
+-
+-static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
+- enum mgmt_result result,
+- const char *error_if_any)
+-{
+- bool success, create_cmt_info_rec;
+-
+- if (!txn->commit_cfg_req)
+- return -1;
+-
+- success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES);
+-
+- /* TODO: these replies should not be send if it's a rollback
+- * b/c right now that is special cased.. that special casing should be
+- * removed; however...
+- */
+- if (!txn->commit_cfg_req->req.commit_cfg.edit &&
+- !txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
+- !txn->commit_cfg_req->req.commit_cfg.rollback &&
+- mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id,
+- txn->commit_cfg_req->req.commit_cfg.src_ds_id,
+- txn->commit_cfg_req->req.commit_cfg.dst_ds_id,
+- txn->commit_cfg_req->req_id,
+- txn->commit_cfg_req->req.commit_cfg.validate_only,
+- txn->commit_cfg_req->req.commit_cfg.unlock, result,
+- error_if_any) != 0) {
+- _log_err("Failed to send COMMIT-CONFIG-REPLY txn-id: %" PRIu64
+- " session-id: %" PRIu64,
+- txn->txn_id, txn->session_id);
+- }
+-
+- if (txn->commit_cfg_req->req.commit_cfg.edit &&
+- mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id,
+- txn->commit_cfg_req->req_id,
+- txn->commit_cfg_req->req.commit_cfg
+- .edit->unlock,
+- true,
+- txn->commit_cfg_req->req.commit_cfg
+- .edit->created,
+- txn->commit_cfg_req->req.commit_cfg
+- .edit->xpath_created,
+- success ? 0 : -1,
+- error_if_any) != 0) {
+- _log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64 " session-id: %" PRIu64,
+- txn->txn_id, txn->session_id);
+- }
+-
+- if (success) {
+- /* Stop the commit-timeout timer */
+- /* XXX why only on success? */
+- event_cancel(&txn->comm_cfg_timeout);
+-
+- create_cmt_info_rec =
+- (result != MGMTD_NO_CFG_CHANGES &&
+- !txn->commit_cfg_req->req.commit_cfg.rollback);
+-
+- /*
+- * Successful commit: Copy Src DS to Dst DS if and only if
+- * this was not a validate-only or abort request.
+- */
+- if ((txn->session_id &&
+- !txn->commit_cfg_req->req.commit_cfg.validate_only &&
+- !txn->commit_cfg_req->req.commit_cfg.abort) ||
+- txn->commit_cfg_req->req.commit_cfg.rollback) {
+- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx,
+- txn->commit_cfg_req->req.commit_cfg.src_ds_ctx,
+- create_cmt_info_rec);
+- }
+-
+- /*
+- * Restore Src DS back to Dest DS only through a commit abort
+- * request.
+- */
+- if (txn->session_id && txn->commit_cfg_req->req.commit_cfg.abort)
+- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx,
+- txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, false);
+- } else {
+- /*
+- * The commit has failied. For implicit commit requests restore
+- * back the contents of the candidate DS. For non-implicit
+- * commit we want to allow the user to re-commit on the changes
+- * (whether further modified or not).
+- */
+- if (txn->commit_cfg_req->req.commit_cfg.implicit ||
+- txn->commit_cfg_req->req.commit_cfg.unlock)
+- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx,
+- txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, false);
+- }
+-
+- if (txn->commit_cfg_req->req.commit_cfg.rollback) {
+- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx);
+- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx);
+- /*
+- * Resume processing the rollback command.
+- *
+- * TODO: there's no good reason to special case rollback, the
+- * rollback boolean should be passed back to the FE client and it
+- * can do the right thing.
+- */
+- mgmt_history_rollback_complete(success);
+- }
+-
+- if (txn->commit_cfg_req->req.commit_cfg.init) {
+- /*
+- * This is the backend init request.
+- * We need to unlock the running datastore.
+- */
+- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx);
+- }
+-
+- txn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL;
+- mgmt_txn_req_free(&txn->commit_cfg_req);
+-
+- /*
+- * The CONFIG Transaction should be destroyed from Frontend-adapter.
+- * But in case the transaction is not triggered from a front-end session
+- * we need to cleanup by itself.
+- */
+- if (!txn->session_id)
+- mgmt_txn_cleanup_txn(&txn);
+-
+- return 0;
+-}
+-
+-static int
+-mgmt_try_move_commit_to_next_phase(struct mgmt_txn_ctx *txn,
+- struct mgmt_commit_cfg_req *cmtcfg_req)
+-{
+- enum mgmt_be_client_id id;
+-
+- _dbg("txn-id: %" PRIu64 ", Phase '%s'", txn->txn_id, mgmt_txn_commit_phase_str(txn));
+-
+- /*
+- * Check if all clients has moved to next phase or not.
+- */
+- FOREACH_MGMTD_BE_CLIENT_ID (id) {
+- if (IS_IDBIT_SET(cmtcfg_req->clients, id) &&
+- cmtcfg_req->be_phase[id] == cmtcfg_req->phase) {
+- /*
+- * There's atleast once client who hasn't moved to
+- * next phase.
+- *
+- * TODO: Need to re-think this design for the case
+- * set of validators for a given YANG data item is
+- * different from the set of notifiers for the same.
+- */
+- return -1;
+- }
+- }
+-
+- /*
+- * If we are here, it means all the clients has moved to next phase.
+- * So we can move the whole commit to next phase.
+- */
+- cmtcfg_req->phase++;
+-
+- _dbg("Move entire txn-id: %" PRIu64 " to phase '%s'", txn->txn_id,
+- mgmt_txn_commit_phase_str(txn));
+-
+- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG);
+-
+- return 0;
+-}
+-
+-/*
+- * This is the real workhorse
+- */
+-static int mgmt_txn_create_config_msgs(struct mgmt_txn_req *txn_req, struct nb_config_cbs *changes)
+-{
+- struct nb_config_cb *cb, *nxt;
+- struct nb_config_change *chg;
+- struct nb_config_cbs mgmtd_changes = { 0 };
+- char *xpath = NULL, *value = NULL;
+- enum mgmt_be_client_id id;
+- struct mgmt_be_client_adapter *adapter;
+- struct mgmt_commit_cfg_req *cmtcfg_req;
+- int num_chgs = 0;
+- uint64_t clients, chg_clients;
+- char op;
+-
+- cmtcfg_req = &txn_req->req.commit_cfg;
+- RB_INIT(nb_config_cbs, &mgmtd_changes);
+-
+- RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) {
+- chg = (struct nb_config_change *)cb;
+-
+- /*
+- * Could have directly pointed to xpath in nb_node.
+- * But dont want to mess with it now.
+- * xpath = chg->cb.nb_node->xpath;
+- */
+- xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0);
+- if (!xpath) {
+- (void)mgmt_txn_send_commit_cfg_reply(
+- txn_req->txn, MGMTD_INTERNAL_ERROR,
+- "Internal error! Could not get Xpath from Ds node!");
+- nb_config_diff_del_changes(&mgmtd_changes);
+- return -1;
+- }
+-
+- value = (char *)lyd_get_value(chg->cb.dnode);
+- if (!value)
+- value = (char *)MGMTD_BE_CONTAINER_NODE_VAL;
+-
+- _dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL");
+-
+- /* Collect changes for mgmtd itself */
+- if (mgmt_is_mgmtd_interested(xpath) &&
+- /* We send tree changes to BEs that we don't need callbacks for */
+- nb_cb_operation_is_valid(cb->operation, cb->dnode->schema)) {
+- uint32_t seq = cb->seq;
+-
+- nb_config_diff_add_change(&mgmtd_changes, cb->operation, &seq, cb->dnode);
+- num_chgs++;
+- }
+-
+- clients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
+- if (!clients) {
+- _dbg("No backends interested in xpath: %s", xpath);
+- continue;
+- }
+-
+- chg_clients = 0;
+- FOREACH_BE_CLIENT_BITS (id, clients) {
+- adapter = mgmt_be_get_adapter_by_id(id);
+- if (!adapter)
+- continue;
+-
+- chg_clients |= (1ull << id);
+-
+- if (!cmtcfg_req->cfg_msgs[id]) {
+- /* Allocate a new config message */
+- struct mgmt_msg_cfg_req *msg;
+-
+- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_req, 0,
+- MTYPE_MSG_NATIVE_CFG_REQ);
+- msg->code = MGMT_MSG_CODE_CFG_REQ;
+- msg->refer_id = txn_req->txn->txn_id;
+- msg->req_id = txn_req->req_id;
+- cmtcfg_req->cfg_msgs[id] = msg;
+- }
+-
+- /*
+- * On the backend, we don't really care if it's CREATE
+- * or MODIFY, because the existence was already checked
+- * on the frontend. Therefore we use SET for both.
+- */
+- op = chg->cb.operation == NB_CB_DESTROY ? 'd' : 'm';
+- darr_push(cmtcfg_req->cfg_actions[id], op);
+-
+- mgmt_msg_native_add_str(cmtcfg_req->cfg_msgs[id], xpath);
+- if (op == 'm')
+- mgmt_msg_native_add_str(cmtcfg_req->cfg_msgs[id], value);
+- cmtcfg_req->num_cfg_data[id]++;
+-
+- _dbg(" -- %s, batch item: %Lu", adapter->name, cmtcfg_req->num_cfg_data[id]);
+-
+- num_chgs++;
+- }
+-
+- if (!chg_clients)
+- _dbg("Daemons interested in XPATH are not currently connected: %s", xpath);
+-
+- cmtcfg_req->clients |= chg_clients;
+-
+- free(xpath);
+- }
+-
+- cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs;
+- if (!num_chgs) {
+- assert(RB_EMPTY(nb_config_cbs, &mgmtd_changes));
+- (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, MGMTD_NO_CFG_CHANGES,
+- "No connected daemons interested in changes");
+- return -1;
+- }
+-
+- if (!RB_EMPTY(nb_config_cbs, &mgmtd_changes)) {
+- /* Create a transaction for local mgmtd config changes */
+- char errmsg[BUFSIZ] = { 0 };
+- size_t errmsg_len = sizeof(errmsg);
+- struct nb_context nb_ctx = { 0 };
+-
+- _dbg("Processing mgmtd bound changes");
+-
+- assert(!cmtcfg_req->mgmtd_nb_txn);
+- nb_ctx.client = NB_CLIENT_MGMTD_SERVER;
+- if (nb_changes_commit_prepare(nb_ctx, mgmtd_changes, "mgmtd-changes", NULL,
+- &cmtcfg_req->mgmtd_nb_txn, errmsg, errmsg_len)) {
+- _log_err("Failed to prepare local config for mgmtd: %s", errmsg);
+- if (cmtcfg_req->mgmtd_nb_txn) {
+- nb_candidate_commit_abort(cmtcfg_req->mgmtd_nb_txn, NULL, 0);
+- cmtcfg_req->mgmtd_nb_txn = NULL;
+- }
+- (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, MGMTD_INTERNAL_ERROR,
+- "Failed to prepare local config for mgmtd");
+- return -1;
+- }
+- assert(cmtcfg_req->mgmtd_nb_txn);
+- }
+-
+- /* Move all BE clients to create phase -- XXX go straight to cfg_req */
+- FOREACH_MGMTD_BE_CLIENT_ID(id) {
+- if (IS_IDBIT_SET(cmtcfg_req->clients, id))
+- cmtcfg_req->be_phase[id] =
+- MGMTD_COMMIT_PHASE_TXN_CREATE;
+- }
+-
+- return 0;
+-}
+-
+-static int mgmt_txn_prepare_config(struct mgmt_txn_ctx *txn)
+-{
+- struct nb_context nb_ctx;
+- struct nb_config *nb_config;
+- struct nb_config_cbs changes;
+- struct nb_config_cbs *cfg_chgs = NULL;
+- int ret;
+- bool del_cfg_chgs = false;
+-
+- ret = 0;
+- memset(&nb_ctx, 0, sizeof(nb_ctx));
+- memset(&changes, 0, sizeof(changes));
+- if (txn->commit_cfg_req->req.commit_cfg.cfg_chgs) {
+- cfg_chgs = txn->commit_cfg_req->req.commit_cfg.cfg_chgs;
+- del_cfg_chgs = true;
+- goto mgmt_txn_prep_config_validation_done;
+- }
+-
+- if (txn->commit_cfg_req->req.commit_cfg.src_ds_id != MGMTD_DS_CANDIDATE) {
+- (void)mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INVALID_PARAM,
+- "Source DS cannot be any other than CANDIDATE!");
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- if (txn->commit_cfg_req->req.commit_cfg.dst_ds_id != MGMTD_DS_RUNNING) {
+- (void)mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INVALID_PARAM,
+- "Destination DS cannot be any other than RUNNING!");
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- if (!txn->commit_cfg_req->req.commit_cfg.src_ds_ctx) {
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM,
+- "No such source datastore!");
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- if (!txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx) {
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM,
+- "No such destination datastore!");
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- if (txn->commit_cfg_req->req.commit_cfg.abort) {
+- /*
+- * This is a commit abort request. Return back success.
+- * That should trigger a restore of Candidate datastore to
+- * Running.
+- */
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL);
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- nb_config = mgmt_ds_get_nb_config(
+- txn->commit_cfg_req->req.commit_cfg.src_ds_ctx);
+- if (!nb_config) {
+- (void)mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- "Unable to retrieve Commit DS Config Tree!");
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- /*
+- * Validate YANG contents of the source DS and get the diff
+- * between source and destination DS contents.
+- */
+- char err_buf[BUFSIZ] = { 0 };
+-
+- ret = nb_candidate_validate_yang(nb_config, true, err_buf,
+- sizeof(err_buf) - 1);
+- if (ret != NB_OK) {
+- if (strncmp(err_buf, " ", strlen(err_buf)) == 0)
+- strlcpy(err_buf, "Validation failed", sizeof(err_buf));
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM,
+- err_buf);
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- nb_config_diff(mgmt_ds_get_nb_config(txn->commit_cfg_req->req.commit_cfg
+- .dst_ds_ctx),
+- nb_config, &changes);
+- cfg_chgs = &changes;
+- del_cfg_chgs = true;
+-
+- if (RB_EMPTY(nb_config_cbs, cfg_chgs)) {
+- /*
+- * This means there's no changes to commit whatsoever
+- * is the source of the changes in config.
+- */
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_NO_CFG_CHANGES,
+- "No changes found to be committed!");
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+-mgmt_txn_prep_config_validation_done:
+-
+- if (mm->perf_stats_en)
+- gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats
+- ->prep_cfg_start,
+- NULL);
+-
+- /*
+- * Iterate over the diffs and create config messages to send to backends.
+- */
+- ret = mgmt_txn_create_config_msgs(txn->commit_cfg_req, cfg_chgs);
+- if (ret != 0) {
+- ret = -1;
+- goto mgmt_txn_prepare_config_done;
+- }
+-
+- /*
+- * If we have BE clients then move to the Transaction Create Phase
+- * otherwise move to the Apply Config Phase to apply mgmtd changes.
+- */
+- if (txn->commit_cfg_req->req.commit_cfg.clients)
+- txn->commit_cfg_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_TXN_CREATE;
+- else
+- txn->commit_cfg_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_APPLY_CFG;
+- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG);
+-
+- /*
+- * Start the COMMIT Timeout Timer to abort Txn if things get stuck at
+- * backend.
+- */
+- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT);
+-mgmt_txn_prepare_config_done:
+-
+- if (cfg_chgs && del_cfg_chgs)
+- nb_config_diff_del_changes(cfg_chgs);
+-
+- return ret;
++ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_GETTREE,
++ sizeof(struct txn_req_get_tree));
++ assert(txn->type == MGMTD_TXN_TYPE_SHOW);
++ return as_get_tree(txn_req);
+ }
+
+-static int mgmt_txn_send_be_txn_create(struct mgmt_txn_ctx *txn)
++static struct txn_req_rpc *txn_req_rpc_alloc(struct mgmt_txn *txn, uint64_t req_id)
+ {
+- enum mgmt_be_client_id id;
+- struct mgmt_be_client_adapter *adapter;
+- struct mgmt_commit_cfg_req *cmtcfg_req;
+-
+- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+-
+- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+- FOREACH_MGMTD_BE_CLIENT_ID (id) {
+- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) {
+- adapter = mgmt_be_get_adapter_by_id(id);
+- if (mgmt_be_send_txn_req(adapter, txn->txn_id, true)) {
+- (void)mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- "Could not send TXN_CREATE to backend adapter");
+- return -1;
+- }
+- }
+- }
+-
+- /*
+- * Dont move the commit to next phase yet. Wait for the TXN_REPLY to
+- * come back.
+- */
+-
+- _dbg("txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'", txn->txn_id, txn->session_id,
+- mgmt_txn_commit_phase_str(txn));
+-
+- return 0;
++ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_RPC,
++ sizeof(struct txn_req_rpc));
++ assert(txn->type == MGMTD_TXN_TYPE_RPC);
++ return as_rpc(txn_req);
+ }
+
+-static int mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn,
+- struct mgmt_be_client_adapter *adapter)
++void txn_req_free(struct txn_req *txn_req)
+ {
+- enum mgmt_be_client_id id = adapter->id;
+- struct mgmt_commit_cfg_req *ccreq;
+- int ret;
+-
+- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+-
+- ccreq = &txn->commit_cfg_req->req.commit_cfg;
+- assert(IS_IDBIT_SET(ccreq->clients, id));
+-
+- /* NUL terminate the actions string */
+- darr_push(ccreq->cfg_actions[id], 0);
+-
+- _dbg("Finished CFG_REQ for '%s' txn-id: %Lu with actions: %s", adapter->name, txn->txn_id,
+- ccreq->cfg_actions[id]);
++ struct mgmt_txn *txn = txn_req->txn;
++ struct txn_req_rpc *rpc;
++ struct txn_req_get_tree *get_tree;
++ uint64_t txn_id = txn->txn_id;
+
+- /* Add the final action string to the message */
+- mgmt_msg_native_add_str(ccreq->cfg_msgs[id], ccreq->cfg_actions[id]);
+- darr_free(ccreq->cfg_actions[id]);
++ /* prevent recursion */
++ _dbg("Deleting %s txn-req req-id: %Lu txn-id: %Lu", txn_req_names[txn_req->req_type],
++ txn_req->req_id, txn_id);
+
+- ret = mgmt_be_send_cfgdata_req(adapter, ccreq->cfg_msgs[id]);
+- mgmt_msg_native_free_msg(ccreq->cfg_msgs[id]);
+- if (ret) {
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INTERNAL_ERROR,
+- "Internal Error! Could not send config data to backend!");
+- return -1;
++ switch (txn_req->req_type) {
++ case TXN_REQ_TYPE_COMMIT:
++ txn_cfg_cleanup(txn_req);
++ break;
++ case TXN_REQ_TYPE_GETTREE:
++ get_tree = as_get_tree(txn_req);
++ lyd_free_all(get_tree->client_results);
++ XFREE(MTYPE_MGMTD_XPATH, get_tree->xpath);
++ break;
++ case TXN_REQ_TYPE_RPC:
++ rpc = as_rpc(txn_req);
++ lyd_free_all(rpc->client_results);
++ XFREE(MTYPE_MGMTD_XPATH, rpc->xpath);
++ break;
+ }
+- ccreq->cmt_stats->last_num_cfgdata_reqs++;
+-
+- /*
+- * We don't advance the phase here, instead that is driven by the
+- * cfg_reply.
+- */
+-
+- return 0;
+-}
+-
+-static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn,
+- struct mgmt_be_client_adapter *adapter)
+-{
+- struct mgmt_commit_cfg_req *cmtcfg_req =
+- &txn->commit_cfg_req->req.commit_cfg;
+-
+- assert(txn->type == MGMTD_TXN_TYPE_CONFIG);
+-
+- if (IS_IDBIT_UNSET(cmtcfg_req->clients, adapter->id))
+- return 0;
+-
+- return mgmt_be_send_txn_req(adapter, txn->txn_id, false);
+-}
+
+-static void mgmt_txn_cfg_commit_timedout(struct event *thread)
+-{
+- struct mgmt_txn_ctx *txn;
+-
+- txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread);
+- assert(txn);
+-
+- assert(txn->type == MGMTD_TXN_TYPE_CONFIG);
+-
+- if (!txn->commit_cfg_req)
+- return;
+-
+- _log_err("Backend timeout txn-id: %" PRIu64 " aborting commit", txn->txn_id);
++ TAILQ_REMOVE(&txn->reqs, txn_req, link);
++ _dbg("Removed req-id: %Lu from request-list", txn_req->req_id);
+
+- /*
+- * Send a COMMIT_CONFIG_REPLY with failure.
+- * NOTE: The transaction cleanup will be triggered from Front-end
+- * adapter.
+- */
+- mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- "Operation on the backend timed-out. Aborting commit!");
++ darr_free(txn_req->err_info);
++ TXN_DECREF(txn_req->txn);
++ XFREE(MTYPE_MGMTD_TXN_REQ, txn_req);
+ }
+
++/* =========================== */
++/* GET TREE (data) BACKEND TXN */
++/* =========================== */
+
+-static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn,
+- struct mgmt_txn_req *txn_req)
++static void txn_get_tree_data_done(struct txn_req_get_tree *get_tree)
+ {
+- struct txn_req_get_tree *get_tree = txn_req->req.get_tree;
++ struct txn_req *txn_req = as_txn_req(get_tree);
++ struct mgmt_txn *txn = txn_req->txn;
+ uint64_t req_id = txn_req->req_id;
+ struct lyd_node *result;
+ int ret = NB_OK;
+
+ /* cancel timer and send reply onward */
+- event_cancel(&txn->get_tree_timeout);
++ event_cancel(&txn_req->timeout);
+
+ if (!get_tree->simple_xpath && get_tree->client_results) {
+ /*
+ * We have a complex query so Filter results by the xpath query.
+ */
+- if (yang_lyd_trim_xpath(&get_tree->client_results,
+- txn_req->req.get_tree->xpath))
++ if (yang_lyd_trim_xpath(&get_tree->client_results, get_tree->xpath))
+ ret = NB_ERR;
+ }
+
+@@ -944,727 +183,110 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn,
+ result = yang_dnode_get(result, get_tree->xpath);
+
+ if (ret == NB_OK)
+- ret = mgmt_fe_adapter_send_tree_data(txn->session_id,
+- txn->txn_id,
+- txn_req->req_id,
+- get_tree->result_type,
+- get_tree->wd_options,
+- result,
+- get_tree->partial_error,
+- false);
+-
+- /* we're done with the request */
+- mgmt_txn_req_free(&txn_req);
+-
+- if (ret) {
+- _log_err("Error sending the results of GETTREE for txn-id %" PRIu64
+- " req_id %" PRIu64 " to requested type %u",
++ mgmt_fe_adapter_send_tree_data(txn->session_id, txn->txn_id, txn_req->req_id,
++ get_tree->result_type, get_tree->wd_options, result,
++ get_tree->partial_error, false);
++ else {
++ _log_err("Error sending the results of GET-TREE for txn-id %Lu req_id %Lu to requested type %u",
+ txn->txn_id, req_id, get_tree->result_type);
+
+ (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false,
+ errno_from_nb_error(ret),
+- "Error converting results of GETTREE");
+- }
+-
+- return ret;
+-}
+-
+-static int txn_rpc_done(struct mgmt_txn_ctx *txn, struct mgmt_txn_req *txn_req)
+-{
+- struct txn_req_rpc *rpc = txn_req->req.rpc;
+- uint64_t req_id = txn_req->req_id;
+-
+- /* cancel timer and send reply onward */
+- event_cancel(&txn->rpc_timeout);
+-
+- if (rpc->errstr)
+- mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -EINVAL,
+- rpc->errstr);
+- else if (mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id,
+- req_id, rpc->result_type,
+- rpc->client_results)) {
+- _log_err("Error sending the results of RPC for txn-id %" PRIu64 " req_id %" PRIu64
+- " to requested type %u",
+- txn->txn_id, req_id, rpc->result_type);
+-
+- (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false,
+- -EINVAL,
+- "Error converting results of RPC");
++ "Error converting results of GET-TREE");
+ }
+
+ /* we're done with the request */
+- mgmt_txn_req_free(&txn_req);
+-
+- return 0;
++ txn_req_free(txn_req);
+ }
+
+ static void txn_get_tree_timeout(struct event *thread)
+ {
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
+-
+- txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread);
+- txn = txn_req->txn;
+-
+- assert(txn);
+- assert(txn->type == MGMTD_TXN_TYPE_SHOW);
+-
++ struct txn_req_get_tree *get_tree = EVENT_ARG(thread);
++ struct mgmt_txn *txn = get_tree->req.txn;
+
+ _log_err("Backend timeout txn-id: %" PRIu64 " ending get-tree", txn->txn_id);
+
+- /*
+- * Send a get-tree data reply.
+- *
+- * NOTE: The transaction cleanup will be triggered from Front-end
+- * adapter.
+- */
+-
+- txn_req->req.get_tree->partial_error = -ETIMEDOUT;
+- txn_get_tree_data_done(txn, txn_req);
++ get_tree->partial_error = -ETIMEDOUT;
++ get_tree->req.err_info = darr_strdup("Get data on the backend timed-out");
++ txn_get_tree_data_done(get_tree);
+ }
+
+-static void txn_rpc_timeout(struct event *thread)
++static void txn_get_tree_handle_error_reply(struct txn_req_get_tree *get_tree,
++ struct mgmt_be_client_adapter *adapter, int error,
++ const char *errstr)
+ {
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
+-
+- txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread);
+- txn = txn_req->txn;
+-
+- assert(txn);
+- assert(txn->type == MGMTD_TXN_TYPE_RPC);
+-
+- _log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id);
++ UNSET_IDBIT(get_tree->clients_wait, adapter->id);
++ get_tree->partial_error = error;
+
+- /*
+- * Send a get-tree data reply.
+- *
+- * NOTE: The transaction cleanup will be triggered from Front-end
+- * adapter.
+- */
+-
+- txn_req->req.rpc->errstr =
+- XSTRDUP(MTYPE_MGMTD_ERR, "Operation on the backend timed-out");
+- txn_rpc_done(txn, txn_req);
++ /* check if done yet */
++ if (!get_tree->clients_wait)
++ txn_get_tree_data_done(get_tree);
+ }
+
+ /*
+- * Send CFG_APPLY_REQs to all the backend client.
+- *
+- * NOTE: This is always dispatched when all CFGDATA_CREATE_REQs
+- * for all backend clients has been generated. Please see
+- * mgmt_txn_register_event() and mgmt_txn_process_commit_cfg()
+- * for details.
++ * Get-tree data from the backend client.
+ */
+-static int mgmt_txn_send_be_cfg_apply(struct mgmt_txn_ctx *txn)
+-{
+- enum mgmt_be_client_id id;
+- struct mgmt_be_client_adapter *adapter;
+- struct mgmt_commit_cfg_req *cmtcfg_req;
+-
+- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+-
+- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+- if (cmtcfg_req->validate_only) {
+- /*
+- * If this was a validate-only COMMIT request return success.
+- */
+- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL);
+- return 0;
+- }
+-
+- /*
+- * Handle mgmtd special case
+- */
+- if (cmtcfg_req->mgmtd_nb_txn) {
+- char errmsg[BUFSIZ] = { 0 };
+-
+- _dbg("Applying mgmtd local bound changes");
+-
+- nb_candidate_commit_apply(cmtcfg_req->mgmtd_nb_txn, false, NULL, errmsg,
+- sizeof(errmsg));
+- cmtcfg_req->mgmtd_nb_txn = NULL;
+- }
+-
+- FOREACH_MGMTD_BE_CLIENT_ID (id) {
+- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) {
+- adapter = mgmt_be_get_adapter_by_id(id);
+- if (!adapter)
+- return -1;
+-
+- if (mgmt_be_send_cfgapply_req(adapter, txn->txn_id)) {
+- (void)mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- "Could not send CFG_APPLY_REQ to backend adapter");
+- return -1;
+- }
+- cmtcfg_req->cmt_stats->last_num_apply_reqs++;
+-
+- UNSET_FLAG(adapter->flags,
+- MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED);
+- }
+- }
+-
+- /*
+- * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES
+- * to come back.
+- */
+-
+- return 0;
+-}
+-
+-static void mgmt_txn_process_commit_cfg(struct event *thread)
++void mgmt_txn_handle_tree_data_reply(struct mgmt_be_client_adapter *adapter,
++ struct mgmt_msg_tree_data *data_msg, size_t msg_len)
+ {
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_commit_cfg_req *cmtcfg_req;
+-
+- txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread);
+- assert(txn);
+-
+- _dbg("Processing COMMIT_CONFIG for txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'",
+- txn->txn_id, txn->session_id, mgmt_txn_commit_phase_str(txn));
+-
+- assert(txn->commit_cfg_req);
+- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+- switch (cmtcfg_req->phase) {
+- case MGMTD_COMMIT_PHASE_PREPARE_CFG:
+- mgmt_txn_prepare_config(txn);
+- break;
+- case MGMTD_COMMIT_PHASE_TXN_CREATE:
+- if (mm->perf_stats_en)
+- gettimeofday(&cmtcfg_req->cmt_stats->txn_create_start,
+- NULL);
+- /*
+- * Send TXN_CREATE_REQ to all Backend now.
+- */
+- mgmt_txn_send_be_txn_create(txn);
+- break;
+- case MGMTD_COMMIT_PHASE_APPLY_CFG:
+- if (mm->perf_stats_en)
+- gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_start,
+- NULL);
+- /*
+- * We should have received successful CFG_VALIDATE_REPLY from
+- * all concerned Backend Clients by now. Send out the
+- * CFG_APPLY_REQs now.
+- */
+- mgmt_txn_send_be_cfg_apply(txn);
+- break;
+- case MGMTD_COMMIT_PHASE_TXN_DELETE:
+- if (mm->perf_stats_en)
+- gettimeofday(&cmtcfg_req->cmt_stats->txn_del_start,
+- NULL);
+- /*
+- * We would have sent TXN_DELETE_REQ to all backend by now.
+- * Send a successful CONFIG_COMMIT_REPLY back to front-end.
+- * NOTE: This should also trigger DS merge/unlock and Txn
+- * cleanup. Please see mgmt_fe_send_commit_cfg_reply() for
+- * more details.
+- */
+- event_cancel(&txn->comm_cfg_timeout);
+- mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL);
+- break;
+- case MGMTD_COMMIT_PHASE_MAX:
+- break;
+- }
+-}
+-
+-static struct mgmt_txn_ctx *
+-mgmt_fe_find_txn_by_session_id(struct mgmt_master *cm, uint64_t session_id,
+- enum mgmt_txn_type type)
+-{
+- struct mgmt_txn_ctx *txn;
+-
+- FOREACH_TXN_IN_LIST (cm, txn) {
+- if (txn->session_id == session_id && txn->type == type)
+- return txn;
+- }
+-
+- return NULL;
+-}
+-
+-static struct mgmt_txn_ctx *mgmt_txn_create_new(uint64_t session_id,
+- enum mgmt_txn_type type)
+-{
+- struct mgmt_txn_ctx *txn = NULL;
++ uint64_t txn_id = data_msg->refer_id;
++ uint64_t req_id = data_msg->req_id;
+
+- /* Do not allow multiple config transactions */
+- if (type == MGMTD_TXN_TYPE_CONFIG && mgmt_config_txn_in_progress())
+- return NULL;
++ enum mgmt_be_client_id id = adapter->id;
++ struct mgmt_txn *txn = txn_lookup(txn_id);
++ struct txn_req *txn_req;
++ struct txn_req_get_tree *get_tree;
++ struct lyd_node *tree = NULL;
++ LY_ERR err;
+
+- txn = mgmt_fe_find_txn_by_session_id(mgmt_txn_mm, session_id, type);
+ if (!txn) {
+- txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn_ctx));
+- assert(txn);
+-
+- txn->session_id = session_id;
+- txn->type = type;
+- mgmt_txns_add_tail(&mgmt_txn_mm->txn_list, txn);
+- /* TODO: why do we need N lists for one transaction */
+- mgmt_txn_reqs_init(&txn->get_tree_reqs);
+- mgmt_txn_reqs_init(&txn->rpc_reqs);
+- txn->commit_cfg_req = NULL;
+- txn->refcount = 0;
+- if (!mgmt_txn_mm->next_txn_id)
+- mgmt_txn_mm->next_txn_id++;
+- txn->txn_id = mgmt_txn_mm->next_txn_id++;
+- hash_get(mgmt_txn_mm->txn_hash, txn, hash_alloc_intern);
+-
+- _dbg("Added new '%s' txn-id: %" PRIu64, mgmt_txn_type2str(type), txn->txn_id);
+-
+- if (type == MGMTD_TXN_TYPE_CONFIG)
+- mgmt_txn_mm->cfg_txn = txn;
+-
+- MGMTD_TXN_LOCK(txn);
+- }
+-
+- return txn;
+-}
+-
+-static void mgmt_txn_delete(struct mgmt_txn_ctx **txn, bool in_hash_free)
+-{
+- MGMTD_TXN_UNLOCK(txn, in_hash_free);
+-}
+-
+-static unsigned int mgmt_txn_hash_key(const void *data)
+-{
+- const struct mgmt_txn_ctx *txn = data;
+-
+- return jhash2((uint32_t *)&txn->txn_id,
+- sizeof(txn->txn_id) / sizeof(uint32_t), 0);
+-}
+-
+-static bool mgmt_txn_hash_cmp(const void *d1, const void *d2)
+-{
+- const struct mgmt_txn_ctx *txn1 = d1;
+- const struct mgmt_txn_ctx *txn2 = d2;
+-
+- return (txn1->txn_id == txn2->txn_id);
+-}
+-
+-static void mgmt_txn_hash_free(void *data)
+-{
+- struct mgmt_txn_ctx *txn = data;
+-
+- mgmt_txn_delete(&txn, true);
+-}
+-
+-static void mgmt_txn_hash_init(void)
+-{
+- if (!mgmt_txn_mm || mgmt_txn_mm->txn_hash)
+- return;
+-
+- mgmt_txn_mm->txn_hash = hash_create(mgmt_txn_hash_key, mgmt_txn_hash_cmp,
+- "MGMT Transactions");
+-}
+-
+-static void mgmt_txn_hash_destroy(void)
+-{
+- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash)
+- return;
+-
+- hash_clean(mgmt_txn_mm->txn_hash, mgmt_txn_hash_free);
+- hash_free(mgmt_txn_mm->txn_hash);
+- mgmt_txn_mm->txn_hash = NULL;
+-}
+-
+-static inline struct mgmt_txn_ctx *mgmt_txn_id2ctx(uint64_t txn_id)
+-{
+- struct mgmt_txn_ctx key = { 0 };
+- struct mgmt_txn_ctx *txn;
+-
+- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash)
+- return NULL;
+-
+- key.txn_id = txn_id;
+- txn = hash_lookup(mgmt_txn_mm->txn_hash, &key);
+-
+- return txn;
+-}
+-
+-uint64_t mgmt_txn_get_session_id(uint64_t txn_id)
+-{
+- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id);
+-
+- return txn ? txn->session_id : MGMTD_SESSION_ID_NONE;
+-}
+-
+-static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line)
+-{
+- txn->refcount++;
+- _dbg("%s:%d --> Lock %s txn-id: %" PRIu64 " refcnt: %d", file, line,
+- mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount);
+-}
+-
+-static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, bool in_hash_free, const char *file, int line)
+-{
+- assert(*txn && (*txn)->refcount);
+-
+- (*txn)->refcount--;
+- _dbg("%s:%d --> Unlock %s txn-id: %" PRIu64 " refcnt: %d", file, line,
+- mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->refcount);
+- if (!(*txn)->refcount) {
+- if ((*txn)->type == MGMTD_TXN_TYPE_CONFIG)
+- if (mgmt_txn_mm->cfg_txn == *txn)
+- mgmt_txn_mm->cfg_txn = NULL;
+- event_cancel(&(*txn)->proc_comm_cfg);
+- event_cancel(&(*txn)->comm_cfg_timeout);
+- event_cancel(&(*txn)->get_tree_timeout);
+- if (!in_hash_free)
+- hash_release(mgmt_txn_mm->txn_hash, *txn);
+-
+- mgmt_txns_del(&mgmt_txn_mm->txn_list, *txn);
+-
+- _dbg("Deleted %s txn-id: %" PRIu64 " session-id: %" PRIu64,
+- mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->session_id);
+-
+- XFREE(MTYPE_MGMTD_TXN, *txn);
+- }
+-
+- *txn = NULL;
+-}
+-
+-static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn)
+-{
+- /* TODO: Any other cleanup applicable */
+-
+- mgmt_txn_delete(txn, false);
+-}
+-
+-static void mgmt_txn_cleanup_all_txns(void)
+-{
+- struct mgmt_txn_ctx *txn;
+-
+- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash)
++ _log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, adapter->name,
++ txn_id);
+ return;
+-
+- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn)
+- mgmt_txn_cleanup_txn(&txn);
+-}
+-
+-static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, enum mgmt_txn_frr_event event)
+-{
+- struct timeval tv = { .tv_sec = 0,
+- .tv_usec = MGMTD_TXN_PROC_DELAY_USEC };
+-
+- assert(mgmt_txn_mm && mgmt_txn_tm);
+-
+- switch (event) {
+- case MGMTD_TXN_EVENT_COMMITCFG:
+- event_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_commit_cfg,
+- txn, &tv, &txn->proc_comm_cfg);
+- break;
+- case MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT:
+- event_add_timer(mgmt_txn_tm, mgmt_txn_cfg_commit_timedout, txn,
+- MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC,
+- &txn->comm_cfg_timeout);
+- break;
+ }
+-}
+-
+-int mgmt_txn_init(struct mgmt_master *m, struct event_loop *loop)
+-{
+- if (mgmt_txn_mm || mgmt_txn_tm)
+- assert(!"MGMTD TXN: Call txn_init() only once");
+-
+- mgmt_txn_mm = m;
+- mgmt_txn_tm = loop;
+- mgmt_txns_init(&m->txn_list);
+- mgmt_txn_hash_init();
+- assert(!m->cfg_txn);
+- m->cfg_txn = NULL;
+-
+- return 0;
+-}
+-
+-void mgmt_txn_destroy(void)
+-{
+- mgmt_txn_cleanup_all_txns();
+- mgmt_txn_hash_destroy();
+-}
+-
+-bool mgmt_config_txn_in_progress(void)
+-{
+- if (mgmt_txn_mm && mgmt_txn_mm->cfg_txn)
+- return true;
+-
+- return false;
+-}
+-
+-uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type)
+-{
+- struct mgmt_txn_ctx *txn;
+-
+- txn = mgmt_txn_create_new(session_id, type);
+- return txn ? txn->txn_id : MGMTD_TXN_ID_NONE;
+-}
+-
+-void mgmt_destroy_txn(uint64_t *txn_id)
+-{
+- struct mgmt_txn_ctx *txn;
+
+- txn = mgmt_txn_id2ctx(*txn_id);
+- if (!txn)
++ /* Find the request. */
++ txn_req = txn_txn_req(txn, req_id);
++ if (!txn_req || txn_req->req_type != TXN_REQ_TYPE_GETTREE) {
++ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64,
++ adapter->name, txn_id, req_id);
+ return;
+-
+- mgmt_txn_delete(&txn, false);
+- *txn_id = MGMTD_TXN_ID_NONE;
+-}
+-
+-int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id,
+- struct mgmt_ds_ctx *src_ds_ctx, enum mgmt_ds_id dst_ds_id,
+- struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort,
+- bool implicit, bool unlock, struct mgmt_edit_req *edit)
+-{
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
+-
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn)
+- return -1;
+-
+- if (txn->commit_cfg_req) {
+- _log_err("Commit already in-progress txn-id: %" PRIu64 " session-id: %" PRIu64
+- ". Cannot start another",
+- txn->txn_id, txn->session_id);
+- return -1;
+- }
+-
+- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_COMMITCFG);
+- txn_req->req.commit_cfg.src_ds_id = src_ds_id;
+- txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx;
+- txn_req->req.commit_cfg.dst_ds_id = dst_ds_id;
+- txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx;
+- txn_req->req.commit_cfg.validate_only = validate_only;
+- txn_req->req.commit_cfg.abort = abort;
+- txn_req->req.commit_cfg.implicit = implicit;
+- txn_req->req.commit_cfg.unlock = unlock;
+- txn_req->req.commit_cfg.edit = edit;
+- txn_req->req.commit_cfg.cmt_stats =
+- mgmt_fe_get_session_commit_stats(txn->session_id);
+-
+- /*
+- * Trigger a COMMIT-CONFIG process.
+- */
+- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG);
+- return 0;
+-}
+-
+-int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter,
+- bool connect)
+-{
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
+- struct mgmt_commit_cfg_req *cmtcfg_req;
+- struct nb_config_cbs *adapter_cfgs = NULL;
+- struct mgmt_ds_ctx *ds_ctx;
+-
+- if (connect) {
+- ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
+- assert(ds_ctx);
+-
+- /*
+- * Lock the running datastore to prevent any changes while we
+- * are initializing the backend.
+- */
+- if (mgmt_ds_lock(ds_ctx, 0) != 0) {
+- _dbg("Failed to lock DS:%s for init of BE adapter '%s'",
+- mgmt_ds_id2name(MGMTD_DS_RUNNING), adapter->name);
+- return -1;
+- }
+-
+- /* Get config for this single backend client */
+- mgmt_be_get_adapter_config(adapter, &adapter_cfgs);
+- if (!adapter_cfgs || RB_EMPTY(nb_config_cbs, adapter_cfgs)) {
+- SET_FLAG(adapter->flags,
+- MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED);
+- mgmt_ds_unlock(ds_ctx);
+- return 0;
+- }
+-
+- /*
+- * Create a CONFIG transaction to push the config changes
+- * provided to the backend client.
+- */
+- txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG);
+- if (!txn) {
+- _log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'",
+- adapter->name);
+- mgmt_ds_unlock(ds_ctx);
+- nb_config_diff_del_changes(adapter_cfgs);
+- return -1;
+- }
+-
+- _dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", txn->txn_id,
+- adapter->name);
+- /*
+- * Set the changeset for transaction to commit and trigger the
+- * commit request.
+- */
+- memset(&adapter->cfg_stats, 0, sizeof(adapter->cfg_stats));
+- txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG);
+- txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_NONE;
+- txn_req->req.commit_cfg.src_ds_ctx = 0;
+- txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING;
+- txn_req->req.commit_cfg.dst_ds_ctx = ds_ctx;
+- txn_req->req.commit_cfg.validate_only = false;
+- txn_req->req.commit_cfg.abort = false;
+- txn_req->req.commit_cfg.init = true;
+- txn_req->req.commit_cfg.cmt_stats = &adapter->cfg_stats;
+- txn_req->req.commit_cfg.cfg_chgs = adapter_cfgs;
+-
+- /*
+- * Trigger a COMMIT-CONFIG process.
+- */
+- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG);
+-
+- } else {
+- /*
+- * Check if any transaction is currently on-going that
+- * involves this backend client. If so, report the transaction
+- * has failed.
+- */
+- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) {
+- /* TODO: update with operational state when that is
+- * completed */
+- if (txn->type == MGMTD_TXN_TYPE_CONFIG) {
+- cmtcfg_req = txn->commit_cfg_req
+- ? &txn->commit_cfg_req->req
+- .commit_cfg
+- : NULL;
+- if (cmtcfg_req && IS_IDBIT_SET(cmtcfg_req->clients,
+- adapter->id)) {
+- mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- "Backend daemon disconnected while processing commit!");
+- }
+- }
+- }
+- }
+-
+- return 0;
+-}
+-
+-int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success,
+- struct mgmt_be_client_adapter *adapter)
+-{
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_commit_cfg_req *cmtcfg_req = NULL;
+-
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG)
+- return -1;
+-
+- if (!create && !txn->commit_cfg_req)
+- return 0;
+-
+- assert(txn->commit_cfg_req);
+- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+- if (create) {
+- if (success) {
+- /*
+- * Done with TXN_CREATE. Move the backend client to
+- * next phase.
+- */
+- assert(cmtcfg_req->phase ==
+- MGMTD_COMMIT_PHASE_TXN_CREATE);
+-
+- /*
+- * Send CFGDATA_CREATE-REQs to the backend immediately.
+- */
+- mgmt_txn_send_be_cfg_data(txn, adapter);
+- } else {
+- mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- "Internal error! Failed to initiate transaction at backend!");
+- }
+- }
+-
+- return 0;
+-}
+-
+-int mgmt_txn_notify_be_cfg_reply(uint64_t txn_id, bool success, const char *error_if_any,
+- struct mgmt_be_client_adapter *adapter)
+-{
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_commit_cfg_req *cmtcfg_req;
+-
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn) {
+- _log_err("CFG_REPLY from '%s' failed no TXN for txn-id: %Lu", adapter->name, txn_id);
+- return -1;
+- }
+- if (txn->type != MGMTD_TXN_TYPE_CONFIG) {
+- _log_err("CFG_REPLY from '%s' failed wrong txn TYPE %u for txn-id: %Lu",
+- adapter->name, txn->type, txn_id);
+- return -1;
+- }
+-
+- if (!txn->commit_cfg_req) {
+- _log_err("CFG_REPLY from '%s' failed no COMMITCFG_REQ for txn-id: %Lu",
+- adapter->name, txn_id);
+- return -1;
+ }
+- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+-
+- if (!success) {
+- _log_err("CFG_REQ sent to '%s' failed txn-id: %" PRIu64 " err: %s", adapter->name,
+- txn->txn_id, error_if_any ? error_if_any : "None");
+- mgmt_txn_send_commit_cfg_reply(txn, MGMTD_VALIDATION_ERROR,
+- error_if_any
+- ? error_if_any
+- : "config validation failed by backend daemon");
+- return 0;
+- }
+-
+- _dbg("CFG_REQ sent to '%s' was successful txn-id: %" PRIu64 " err: %s", adapter->name,
+- txn->txn_id, error_if_any ? error_if_any : "None");
+-
+- cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_APPLY_CFG;
++ get_tree = as_get_tree(txn_req);
+
+- mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+-
+- return 0;
+-}
+-
+-int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success,
+- char *error_if_any,
+- struct mgmt_be_client_adapter *adapter)
+-{
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_commit_cfg_req *cmtcfg_req = NULL;
+-
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG || !txn->commit_cfg_req)
+- return -1;
+-
+- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+-
+- if (!success) {
+- _log_err("CFGDATA_APPLY_REQ sent to '%s' failed txn-id: %" PRIu64 " err: %s",
+- adapter->name, txn->txn_id, error_if_any ? error_if_any : "None");
+- mgmt_txn_send_commit_cfg_reply(
+- txn, MGMTD_INTERNAL_ERROR,
+- error_if_any
+- ? error_if_any
+- : "Internal error! Failed to apply config data on backend!");
+- return 0;
++ /* store the result */
++ err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result,
++ data_msg->result_type, LYD_PARSE_STRICT | LYD_PARSE_ONLY,
++ 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree);
++ if (err) {
++ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
++ " error parsing result of type %u",
++ adapter->name, txn_id, req_id, data_msg->result_type);
+ }
++ if (!err) {
++ /* TODO: we could merge ly_errs here if it's not binary */
+
+- cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_TXN_DELETE;
+-
+- /*
+- * All configuration for the specific backend has been applied.
+- * Send TXN-DELETE to wrap up the transaction for this backend.
+- */
+- SET_FLAG(adapter->flags, MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED);
+- mgmt_txn_send_be_txn_delete(txn, adapter);
++ if (!get_tree->client_results)
++ get_tree->client_results = tree;
++ else
++ err = lyd_merge_siblings(&get_tree->client_results, tree,
++ LYD_MERGE_DESTRUCT);
++ if (err) {
++ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
++ " error merging result",
++ adapter->name, txn_id, req_id);
++ }
++ }
++ if (!get_tree->partial_error)
++ get_tree->partial_error = (data_msg->partial_error ? data_msg->partial_error
++ : (int)err);
+
+- mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+- if (mm->perf_stats_en)
+- gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_end, NULL);
++ if (!data_msg->more)
++ UNSET_IDBIT(get_tree->clients_wait, id);
+
+- return 0;
++ /* check if done */
++ if (!get_tree->clients_wait)
++ txn_get_tree_data_done(get_tree);
+ }
+
+ /**
+@@ -1676,21 +298,19 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+ uint32_t wd_options, bool simple_xpath, struct lyd_node **ylib,
+ const char *xpath)
+ {
++ struct mgmt_be_client_adapter *adapter;
+ struct mgmt_msg_get_tree *msg;
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
+ struct txn_req_get_tree *get_tree;
++ struct mgmt_txn *txn;
+ enum mgmt_be_client_id id;
+ ssize_t slen = strlen(xpath);
+ int ret;
+
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn)
+- return -1;
++ txn = txn_lookup(txn_id);
++ assert(txn && txn->type == MGMTD_TXN_TYPE_SHOW);
+
+ /* If error in this function below here, be sure to free the req */
+- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE);
+- get_tree = txn_req->req.get_tree;
++ get_tree = txn_req_get_tree_alloc(txn, req_id);
+ get_tree->result_type = result_type;
+ get_tree->wd_options = wd_options;
+ get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT);
+@@ -1702,10 +322,9 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+ * If the requested datastore is operational, get the config
+ * from running.
+ */
+- struct mgmt_ds_ctx *ds =
+- mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL
+- ? MGMTD_DS_RUNNING
+- : ds_id);
++ struct mgmt_ds_ctx *ds = mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL
++ ? MGMTD_DS_RUNNING
++ : ds_id);
+ struct nb_config *config = mgmt_ds_get_nb_config(ds);
+
+ if (config) {
+@@ -1726,18 +345,16 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+ */
+ if (set->count == 1) {
+ err = lyd_dup_single(set->dnodes[0], NULL,
+- LYD_DUP_WITH_PARENTS |
+- LYD_DUP_WITH_FLAGS |
++ LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS |
+ LYD_DUP_RECURSIVE,
+ &get_tree->client_results);
+ if (!err)
+ while (get_tree->client_results->parent)
+- get_tree->client_results = lyd_parent(
+- get_tree->client_results);
++ get_tree->client_results =
++ lyd_parent(get_tree->client_results);
+ } else if (set->count > 1) {
+ err = lyd_dup_siblings(config->dnode, NULL,
+- LYD_DUP_RECURSIVE |
+- LYD_DUP_WITH_FLAGS,
++ LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS,
+ &get_tree->client_results);
+ if (!err)
+ get_tree->simple_xpath = false;
+@@ -1762,9 +379,10 @@ state:
+ }
+
+ /* If we are only getting config, we are done */
+- if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) ||
+- ds_id != MGMTD_DS_OPERATIONAL || !clients)
+- return txn_get_tree_data_done(txn, txn_req);
++ if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || ds_id != MGMTD_DS_OPERATIONAL || !clients) {
++ txn_get_tree_data_done(get_tree);
++ return 0;
++ }
+
+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1,
+ MTYPE_MSG_NATIVE_GET_TREE);
+@@ -1776,99 +394,146 @@ state:
+ strlcpy(msg->xpath, xpath, slen + 1);
+
+ assert(clients);
+- FOREACH_BE_CLIENT_BITS (id, clients) {
+- ret = mgmt_be_send_native(id, msg);
++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) {
++ ret = mgmt_be_adapter_send(adapter, msg);
+ if (ret) {
+- _log_err("Could not send get-tree message to backend client %s",
+- mgmt_be_client_id2name(id));
++ darr_in_sprintf(get_tree->req.err_info, "Failed to send GET-DATA to %s",
++ adapter->name);
+ continue;
+ }
+-
+- _dbg("Sent get-tree req to backend client %s", mgmt_be_client_id2name(id));
+-
+- /* record that we sent the request to the client */
+- get_tree->sent_clients |= (1u << id);
++ SET_IDBIT(get_tree->clients, id);
+ }
+
+ mgmt_msg_native_free_msg(msg);
+
+- /* Return if we didn't send any messages to backends */
+- if (!get_tree->sent_clients)
+- return txn_get_tree_data_done(txn, txn_req);
+-
+- /* Start timeout timer - pulled out of register event code so we can
+- * pass a different arg
+- */
+- event_add_timer(mgmt_txn_tm, txn_get_tree_timeout, txn_req,
+- MGMTD_TXN_GET_TREE_MAX_DELAY_SEC,
+- &txn->get_tree_timeout);
++ get_tree->clients_wait = get_tree->clients;
++ if (!get_tree->clients_wait)
++ txn_get_tree_data_done(get_tree);
++ else
++ event_add_timer(mm->master, txn_get_tree_timeout, get_tree,
++ MGMTD_TXN_GET_TREE_MAX_DELAY_SEC, &get_tree->req.timeout);
+ return 0;
+ }
+
+-int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id ds_id,
+- struct mgmt_ds_ctx *ds_ctx, enum mgmt_ds_id commit_ds_id,
+- struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit,
+- LYD_FORMAT request_type, uint8_t flags, uint8_t operation,
+- const char *xpath, const char *data)
++/* =============== */
++/* RPC BACKEND TXN */
++/* =============== */
++
++static void txn_rpc_done(struct txn_req_rpc *rpc)
+ {
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_edit_req *edit;
+- struct nb_config *nb_config;
+- char errstr[BUFSIZ];
+- int ret;
++ struct txn_req *txn_req = as_txn_req(rpc);
++ struct mgmt_txn *txn = txn_req->txn;
++ uint64_t req_id = rpc->req.req_id;
+
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn)
+- return -1;
++ /* cancel timer and send reply onward */
++ event_cancel(&rpc->req.timeout);
++
++ if (txn_req->err_info)
++ mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, txn_req->error ?: -EINVAL,
++ txn_req->err_info);
++ else
++ mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, req_id,
++ rpc->result_type, rpc->client_results);
+
+- edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req));
++ /* we're done with the request */
++ txn_req_free(txn_req);
++}
++
++static void txn_rpc_timeout(struct event *event)
++{
++ struct txn_req_rpc *rpc = EVENT_ARG(event);
++ struct mgmt_txn *txn = rpc->req.txn;
+
+- nb_config = mgmt_ds_get_nb_config(ds_ctx);
+- assert(nb_config);
++ _log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id);
+
+- /* XXX Should we do locking here? */
++ rpc->req.error = -ETIMEDOUT;
++ rpc->req.err_info = darr_strdup("RPC on the backend timed-out");
++ txn_rpc_done(rpc);
++}
+
+- ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath,
+- data, &edit->created, edit->xpath_created,
+- errstr, sizeof(errstr));
+- if (ret)
+- goto reply;
++static void txn_rpc_handle_error_reply(struct txn_req_rpc *rpc,
++ struct mgmt_be_client_adapter *adapter, int error,
++ const char *errstr)
++{
++ UNSET_IDBIT(rpc->clients_wait, adapter->id);
++ if (errstr)
++ darr_in_strdup(rpc->req.err_info, errstr);
++ /* check if done */
++ if (!rpc->clients_wait)
++ txn_rpc_done(rpc);
++}
+
+- if (commit) {
+- edit->unlock = unlock;
++void mgmt_txn_handle_rpc_reply(struct mgmt_be_client_adapter *adapter,
++ struct mgmt_msg_rpc_reply *reply_msg, size_t msg_len)
++{
++ uint64_t txn_id = reply_msg->refer_id;
++ uint64_t req_id = reply_msg->req_id;
++ enum mgmt_be_client_id id = adapter->id;
++ struct mgmt_txn *txn = txn_lookup(txn_id);
++ struct txn_req *txn_req;
++ struct txn_req_rpc *rpc;
++ struct lyd_node *tree;
++ size_t data_len = msg_len - sizeof(*reply_msg);
++ LY_ERR err = LY_SUCCESS;
+
+- mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, ds_ctx, commit_ds_id,
+- commit_ds_ctx, false, false, true, false, edit);
+- return 0;
++ if (!txn) {
++ _log_err("RPC reply from %s for a missing txn-id %Lu", adapter->name, txn_id);
++ return;
+ }
+-reply:
+- mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, req_id,
+- unlock, commit, edit->created,
+- edit->xpath_created,
+- errno_from_nb_error(ret), errstr);
+
+- XFREE(MTYPE_MGMTD_TXN_REQ, edit);
++ /* Find the request. */
++ txn_req = txn_txn_req(txn, req_id);
++ if (!txn_req || txn_req->req_type != TXN_REQ_TYPE_RPC) {
++ _log_err("RPC reply from %s for txn-id %Lu missing req_id %Lu", adapter->name,
++ txn_id, req_id);
++ return;
++ }
++ rpc = as_rpc(txn_req);
+
+- return 0;
++ tree = NULL;
++ if (data_len)
++ err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, reply_msg->data, true,
++ &tree);
++ if (err) {
++ _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
++ " error parsing result of type %u: %s",
++ adapter->name, txn_id, req_id, reply_msg->result_type, ly_strerrcode(err));
++ }
++ if (!err && tree) {
++ if (!rpc->client_results)
++ rpc->client_results = tree;
++ else
++ err = lyd_merge_siblings(&rpc->client_results, tree, LYD_MERGE_DESTRUCT);
++ if (err) {
++ _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
++ " error merging result: %s",
++ adapter->name, txn_id, req_id, ly_strerrcode(err));
++ }
++ }
++ if (err)
++ darr_in_strdup(rpc->req.err_info, "Cannot parse result from the backend");
++
++ UNSET_IDBIT(rpc->clients_wait, id);
++
++ /* check if done yet */
++ if (!rpc->clients_wait)
++ txn_rpc_done(rpc);
+ }
+
+-int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+- LYD_FORMAT result_type, const char *xpath,
+- const char *data, size_t data_len)
++void mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type,
++ const char *xpath, const char *data, size_t data_len)
+ {
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
++ struct mgmt_be_client_adapter *adapter;
++ struct mgmt_txn *txn;
+ struct mgmt_msg_rpc *msg;
+ struct txn_req_rpc *rpc;
+- uint64_t id;
++ enum mgmt_be_client_id id;
+ int ret;
+
+- txn = mgmt_txn_id2ctx(txn_id);
+- if (!txn)
+- return -1;
++ txn = txn_lookup(txn_id);
++ assert(txn && txn->type == MGMTD_TXN_TYPE_RPC);
+
+- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_RPC);
+- rpc = txn_req->req.rpc;
++ rpc = txn_req_rpc_alloc(txn, req_id);
+ rpc->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath);
+ rpc->result_type = result_type;
+
+@@ -1884,38 +549,37 @@ int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+ mgmt_msg_native_append(msg, data, data_len);
+
+ assert(clients);
+- FOREACH_BE_CLIENT_BITS (id, clients) {
+- ret = mgmt_be_send_native(id, msg);
++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) {
++ ret = mgmt_be_adapter_send(adapter, msg);
+ if (ret) {
+- _log_err("Could not send rpc message to backend client %s",
+- mgmt_be_client_id2name(id));
++ darr_in_sprintf(rpc->req.err_info, "Failed to send RPC to %s",
++ adapter->name);
+ continue;
+ }
+-
+- _dbg("Sent rpc req to backend client %s", mgmt_be_client_id2name(id));
+-
+- /* record that we sent the request to the client */
+- rpc->sent_clients |= (1u << id);
++ SET_IDBIT(rpc->clients, id);
+ }
+
+ mgmt_msg_native_free_msg(msg);
+
+- if (!rpc->sent_clients)
+- return txn_rpc_done(txn, txn_req);
+-
+- event_add_timer(mgmt_txn_tm, txn_rpc_timeout, txn_req,
+- MGMTD_TXN_RPC_MAX_DELAY_SEC, &txn->rpc_timeout);
+-
+- return 0;
++ rpc->clients_wait = rpc->clients;
++ if (!rpc->clients_wait)
++ txn_rpc_done(rpc);
++ else
++ event_add_timer(mm->master, txn_rpc_timeout, rpc, MGMTD_TXN_RPC_MAX_DELAY_SEC,
++ &rpc->req.timeout);
+ }
+
+-int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients,
+- const char **selectors)
++/* =========================== */
++/* NOTIFY SELECTOR BACKEND TXN */
++/* =========================== */
++
++void mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients,
++ const char **selectors)
+ {
++ struct mgmt_be_client_adapter *adapter;
+ struct mgmt_msg_notify_select *msg;
++ enum mgmt_be_client_id id;
+ char **all_selectors = NULL;
+- uint64_t id;
+- int ret;
+ uint i;
+
+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_select, 0,
+@@ -1936,352 +600,255 @@ int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_
+ mgmt_msg_native_add_str(msg, selectors[i]);
+
+ assert(clients);
+- FOREACH_BE_CLIENT_BITS (id, clients) {
+- /* make sure the backend is running/connected */
+- if (!mgmt_be_get_adapter_by_id(id))
+- continue;
+- ret = mgmt_be_send_native(id, msg);
+- if (ret) {
+- _log_err("Could not send notify-select message to backend client %s",
+- mgmt_be_client_id2name(id));
++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) {
++ if (mgmt_be_adapter_send(adapter, msg)) {
++ /*
++ * Failed to send to this backend since we require
++ * reliable notifications from the backends we need to
++ * disconnect here.
++ */
++ msg_conn_disconnect(adapter->conn, false);
+ continue;
+ }
+-
+- _dbg("Sent notify-select req to backend client %s", mgmt_be_client_id2name(id));
+ }
+ mgmt_msg_native_free_msg(msg);
+
+ if (all_selectors)
+ darr_free_free(all_selectors);
+-
+- return 0;
+ }
+
++/* =============== */
++/* TXN ERROR REPLY */
++/* =============== */
++
+ /*
+ * Error reply from the backend client.
++ *
++ * NOTE: can disconnect (and delete) the backend.
+ */
+-int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter,
+- uint64_t txn_id, uint64_t req_id, int error,
+- const char *errstr)
++void mgmt_txn_handle_error_reply(struct mgmt_be_client_adapter *adapter, uint64_t txn_id,
++ uint64_t req_id, int error, const char *errstr)
+ {
+- enum mgmt_be_client_id id = adapter->id;
+- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id);
+- struct txn_req_get_tree *get_tree;
+- struct txn_req_rpc *rpc;
+- struct mgmt_txn_req *txn_req;
++ struct txn_req *txn_req;
++ struct mgmt_txn *txn;
+
++ txn = txn_lookup(txn_id);
+ if (!txn) {
+- _log_err("Error reply from %s cannot find txn-id %" PRIu64, adapter->name, txn_id);
+- return -1;
++ _log_err("Error reply from %s cannot find txn-id %Lu", adapter->name, txn_id);
++ return;
+ }
+
+- if (txn->type == MGMTD_TXN_TYPE_CONFIG) {
+- /*
+- * Handle an error during a configuration transaction.
+- */
+- txn_req = txn->commit_cfg_req;
+- if (!txn_req) {
+- _log_err("Error reply from %s for txn-id %Lu req_id %Lu but no commit config request",
+- adapter->name, txn_id, req_id);
+- return -1;
+- }
+- /*
+- * Most of the C client code doesn't set req_id so handle this.
+- * If we ever wanted to support multiple active CFG_REQ for under
+- * txn (unlikely), we would need to fix this.
+- */
+- if (req_id != 0 && txn_req->req_id != req_id) {
+- _log_err("Error reply from %s for txn-id %Lu req_id %Lu does not match current config req_id %Lu",
+- adapter->name, txn_id, req_id, txn_req->req_id);
+- return -1;
+- }
+- switch (txn_req->req.commit_cfg.phase) {
+- case MGMTD_COMMIT_PHASE_TXN_CREATE:
+- /* This is a validation error.*/
+- return mgmt_txn_notify_be_cfg_reply(txn_id, false, errstr, adapter);
+- case MGMTD_COMMIT_PHASE_PREPARE_CFG:
+- case MGMTD_COMMIT_PHASE_APPLY_CFG:
+- case MGMTD_COMMIT_PHASE_TXN_DELETE:
+- /* Drop the connection these errors should never happen */
+- msg_conn_disconnect(adapter->conn, false);
++ TAILQ_FOREACH (txn_req, &txn->reqs, link) {
++ /* Clients do not set req_id for commit errors yet there's only
++ * one so this works for now. */
++ if (req_id == 0 && txn_req->req_type == TXN_REQ_TYPE_COMMIT)
+ break;
+- case MGMTD_COMMIT_PHASE_MAX:
+- assert(!"should never have this phase");
+- }
+- return 0;
+- }
+-
+- /*
+- * Find the non-config request.
+- */
+-
+- FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req)
+- if (txn_req->req_id == req_id)
++ else if (txn_req->req_id == req_id)
+ break;
+- if (!txn_req)
+- FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req)
+- if (txn_req->req_id == req_id)
+- break;
+-
++ }
+ if (!txn_req) {
+- _log_err("Error reply from %s for txn-id %" PRIu64 " cannot find req_id %" PRIu64,
++ _log_err("Error reply from %s for txn-id %Lu cannot find req_id %Lu",
+ adapter->name, txn_id, req_id);
+- return -1;
++ return;
+ }
+
+- _log_err("Error reply from %s for txn-id %" PRIu64 " req_id %" PRIu64, adapter->name,
+- txn_id, req_id);
++ _log_err("Error reply from %s for txn-id %Lu req_id %Lu", adapter->name, txn_id, req_id);
+
+ switch (txn_req->req_type) {
+- case MGMTD_TXN_PROC_GETTREE:
+- get_tree = txn_req->req.get_tree;
+- get_tree->recv_clients |= (1u << id);
+- get_tree->partial_error = error;
+-
+- /* check if done yet */
+- if (get_tree->recv_clients != get_tree->sent_clients)
+- return 0;
+- return txn_get_tree_data_done(txn, txn_req);
+- case MGMTD_TXN_PROC_RPC:
+- rpc = txn_req->req.rpc;
+- rpc->recv_clients |= (1u << id);
+- if (errstr) {
+- XFREE(MTYPE_MGMTD_ERR, rpc->errstr);
+- rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, errstr);
+- }
+- /* check if done yet */
+- if (rpc->recv_clients != rpc->sent_clients)
+- return 0;
+- return txn_rpc_done(txn, txn_req);
+-
++ case TXN_REQ_TYPE_COMMIT:
++ txn_cfg_handle_error(txn_req, adapter, error, errstr);
++ return;
++ case TXN_REQ_TYPE_GETTREE:
++ txn_get_tree_handle_error_reply(as_get_tree(txn_req), adapter, error, errstr);
++ return;
++ case TXN_REQ_TYPE_RPC:
++ txn_rpc_handle_error_reply(as_rpc(txn_req), adapter, error, errstr);
++ return;
+ /* non-native message events */
+- case MGMTD_TXN_PROC_COMMITCFG:
+ default:
+ assert(!"non-native req type in native error path");
+- return -1;
+ }
+ }
+
+-/*
+- * Get-tree data from the backend client.
+- */
+-int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter,
+- struct mgmt_msg_tree_data *data_msg,
+- size_t msg_len)
++/* ============== */
++/* Txn Management */
++/* ============== */
++
++static unsigned int txn_txn_id_hash(const struct mgmt_txn *txn)
+ {
+- uint64_t txn_id = data_msg->refer_id;
+- uint64_t req_id = data_msg->req_id;
++ uint64_t key = txn->txn_id;
+
+- enum mgmt_be_client_id id = adapter->id;
+- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id);
+- struct mgmt_txn_req *txn_req;
+- struct txn_req_get_tree *get_tree;
+- struct lyd_node *tree = NULL;
+- LY_ERR err;
++ return (uint32_t)key ^ (uint32_t)(key >> 32);
++}
+
+- if (!txn) {
+- _log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, adapter->name,
+- txn_id);
+- return -1;
+- }
++static bool txn_txn_id_cmp(const struct mgmt_txn *a, const struct mgmt_txn *b)
++{
++ return (a->txn_id == b->txn_id);
++}
+
+- /* Find the request. */
+- FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req)
+- if (txn_req->req_id == req_id)
+- break;
+- if (!txn_req) {
+- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64,
+- adapter->name, txn_id, req_id);
+- return -1;
+- }
++struct mgmt_txn *txn_lookup(uint64_t txn_id)
++{
++ struct mgmt_txn key = { 0 };
+
+- get_tree = txn_req->req.get_tree;
++ if (!txn_id_tab)
++ return NULL;
++ key.txn_id = txn_id;
++ return hash_lookup(txn_id_tab, &key);
++}
+
+- /* store the result */
+- err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result,
+- data_msg->result_type,
+- LYD_PARSE_STRICT | LYD_PARSE_ONLY,
+- 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree);
+- if (err) {
+- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
+- " error parsing result of type %u",
+- adapter->name, txn_id, req_id, data_msg->result_type);
+- }
+- if (!err) {
+- /* TODO: we could merge ly_errs here if it's not binary */
++uint64_t mgmt_txn_get_session_id(uint64_t txn_id)
++{
++ struct mgmt_txn *txn = txn_lookup(txn_id);
+
+- if (!get_tree->client_results)
+- get_tree->client_results = tree;
+- else
+- err = lyd_merge_siblings(&get_tree->client_results,
+- tree, LYD_MERGE_DESTRUCT);
+- if (err) {
+- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
+- " error merging result",
+- adapter->name, txn_id, req_id);
+- }
++ return txn ? txn->session_id : MGMTD_SESSION_ID_NONE;
++}
++
++static void txn_incref(struct mgmt_txn *txn, const char *file, int line)
++{
++ txn->refcount++;
++ _dbg("TXN-INCREF %s txn-id: %Lu refcnt: %d file: %s line: %d)",
++ mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount, file, line);
++}
++
++void txn_decref(struct mgmt_txn *txn, const char *file, int line)
++{
++ assert(txn && txn->refcount);
++
++ txn->refcount--;
++ _dbg("TXN-DECREF %s txn-id: %Lu refcnt: %d file: %s line: %d",
++ mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount, file, line);
++ if (!txn->refcount) {
++ if (txn->type == MGMTD_TXN_TYPE_CONFIG)
++ if (txn_config_txn == txn)
++ txn_config_txn = NULL;
++ hash_release(txn_id_tab, txn);
++ TAILQ_REMOVE(&txn_txns, txn, link);
++
++ _dbg("Deleted %s txn-id: %Lu session-id: %Lu", mgmt_txn_type2str(txn->type),
++ txn->txn_id, txn->session_id);
++
++ XFREE(MTYPE_MGMTD_TXN, txn);
+ }
+- if (!get_tree->partial_error)
+- get_tree->partial_error = (data_msg->partial_error
+- ? data_msg->partial_error
+- : (int)err);
++}
+
+- if (!data_msg->more)
+- get_tree->recv_clients |= (1u << id);
++struct mgmt_txn *txn_create(enum mgmt_txn_type type)
++{
++ struct mgmt_txn *txn;
+
+- /* check if done yet */
+- if (get_tree->recv_clients != get_tree->sent_clients)
+- return 0;
++ txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn));
++ txn->type = type;
++ TAILQ_INSERT_TAIL(&txn_txns, txn, link);
++ TAILQ_INIT(&txn->reqs);
++ txn->txn_id = txn_next_id++;
++ hash_get(txn_id_tab, txn, hash_alloc_intern);
++
++ _dbg("Added new '%s' txn-id: %" PRIu64, mgmt_txn_type2str(type), txn->txn_id);
+
+- return txn_get_tree_data_done(txn, txn_req);
++ TXN_INCREF(txn);
++ return txn;
+ }
+
+-int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter,
+- struct mgmt_msg_rpc_reply *reply_msg,
+- size_t msg_len)
++uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type)
+ {
+- uint64_t txn_id = reply_msg->refer_id;
+- uint64_t req_id = reply_msg->req_id;
+- enum mgmt_be_client_id id = adapter->id;
+- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id);
+- struct mgmt_txn_req *txn_req;
+- struct txn_req_rpc *rpc;
+- struct lyd_node *tree;
+- size_t data_len = msg_len - sizeof(*reply_msg);
+- LY_ERR err = LY_SUCCESS;
++ struct mgmt_txn *txn;
+
+- if (!txn) {
+- _log_err("RPC reply from %s for a missing txn-id %" PRIu64, adapter->name, txn_id);
+- return -1;
+- }
++ /* Do not allow multiple (external) config transactions */
++ if (type == MGMTD_TXN_TYPE_CONFIG && txn_config_txn)
++ return MGMTD_TXN_ID_NONE;
+
+- /* Find the request. */
+- FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req)
+- if (txn_req->req_id == req_id)
+- break;
+- if (!txn_req) {
+- _log_err("RPC reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64,
+- adapter->name, txn_id, req_id);
+- return -1;
+- }
++ /* Find existing txn for this session and type */
++ TAILQ_FOREACH (txn, &txn_txns, link)
++ if (txn->session_id == session_id && txn->type == type)
++ return txn->txn_id;
+
+- rpc = txn_req->req.rpc;
++ txn = txn_create(type);
++ txn->session_id = session_id;
++ if (type == MGMTD_TXN_TYPE_CONFIG)
++ txn_config_txn = txn;
++ return txn->txn_id;
++}
+
+- tree = NULL;
+- if (data_len)
+- err = yang_parse_rpc(rpc->xpath, reply_msg->result_type,
+- reply_msg->data, true, &tree);
+- if (err) {
+- _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
+- " error parsing result of type %u: %s",
+- adapter->name, txn_id, req_id, reply_msg->result_type, ly_strerrcode(err));
+- }
+- if (!err && tree) {
+- if (!rpc->client_results)
+- rpc->client_results = tree;
+- else
+- err = lyd_merge_siblings(&rpc->client_results, tree,
+- LYD_MERGE_DESTRUCT);
+- if (err) {
+- _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64
+- " error merging result: %s",
+- adapter->name, txn_id, req_id, ly_strerrcode(err));
+- }
+- }
+- if (err) {
+- XFREE(MTYPE_MGMTD_ERR, rpc->errstr);
+- rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR,
+- "Cannot parse result from the backend");
+- }
++void mgmt_destroy_txn(uint64_t *txn_id)
++{
++ struct mgmt_txn *txn;
+
+- rpc->recv_clients |= (1u << id);
++ if (*txn_id == MGMTD_TXN_ID_NONE)
++ return;
+
+- /* check if done yet */
+- if (rpc->recv_clients != rpc->sent_clients)
+- return 0;
++ txn = txn_lookup(*txn_id);
++ if (!txn)
++ return;
++
++ TXN_DECREF(txn);
++ *txn_id = MGMTD_TXN_ID_NONE;
++}
++
++bool mgmt_txn_config_in_progress(void)
++{
++ return txn_config_txn != NULL;
++}
++
++int mgmt_txn_handle_be_adapter_connect(struct mgmt_be_client_adapter *adapter, bool connect)
++{
++ struct mgmt_txn *txn, *next;
+
+- return txn_rpc_done(txn, txn_req);
++ if (connect)
++ txn_cfg_be_client_connect(adapter);
++ else {
++ /*
++ * Check if any transaction is currently on-going that
++ * involves this backend client. If so check if we can now
++ * advance that configuration.
++ */
++ TAILQ_FOREACH_SAFE (txn, &txn_txns, link, next) {
++ /* XXX update to handle get-tree and RPC too! */
++ if (txn->type == MGMTD_TXN_TYPE_CONFIG)
++ txn_cfg_txn_be_client_disconnect(txn, adapter);
++ }
++ }
++
++ return 0;
+ }
+
+ void mgmt_txn_status_write(struct vty *vty)
+ {
+- struct mgmt_txn_ctx *txn;
++ struct mgmt_txn *txn;
++ uint count = 0;
+
+ vty_out(vty, "MGMTD Transactions\n");
+
+- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) {
++ TAILQ_FOREACH (txn, &txn_txns, link) {
+ vty_out(vty, " Txn: \t\t\t0x%p\n", txn);
+ vty_out(vty, " Txn-Id: \t\t\t%" PRIu64 "\n", txn->txn_id);
+- vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n",
+- txn->session_id);
+- vty_out(vty, " Type: \t\t\t%s\n",
+- mgmt_txn_type2str(txn->type));
++ vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n", txn->session_id);
++ vty_out(vty, " Type: \t\t\t%s\n", mgmt_txn_type2str(txn->type));
+ vty_out(vty, " Ref-Count: \t\t\t%d\n", txn->refcount);
++ count++;
+ }
+- vty_out(vty, " Total: %d\n",
+- (int)mgmt_txns_count(&mgmt_txn_mm->txn_list));
++ vty_out(vty, " Total: %u\n", count);
+ }
+
+-int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx,
+- struct mgmt_ds_ctx *dst_ds_ctx)
++void mgmt_txn_init(void)
+ {
+- static struct nb_config_cbs changes;
+- static struct mgmt_commit_stats dummy_stats;
+-
+- struct nb_config_cbs *cfg_chgs = NULL;
+- struct mgmt_txn_ctx *txn;
+- struct mgmt_txn_req *txn_req;
+-
+- memset(&changes, 0, sizeof(changes));
+- memset(&dummy_stats, 0, sizeof(dummy_stats));
+- /*
+- * This could be the case when the config is directly
+- * loaded onto the candidate DS from a file. Get the
+- * diff from a full comparison of the candidate and
+- * running DSs.
+- */
+- nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx),
+- mgmt_ds_get_nb_config(src_ds_ctx), &changes);
+- cfg_chgs = &changes;
+-
+- if (RB_EMPTY(nb_config_cbs, cfg_chgs)) {
+- /*
+- * This means there's no changes to commit whatsoever
+- * is the source of the changes in config.
+- */
+- return -1;
+- }
++ assert(txn_next_id == 0);
++ txn_next_id = 1;
++ txn_id_tab = hash_create((unsigned int (*)(const void *))txn_txn_id_hash,
++ (bool (*)(const void *, const void *))txn_txn_id_cmp,
++ "Mgmt Txns by txn-id");
++}
+
+- /*
+- * Create a CONFIG transaction to push the config changes
+- * provided to the backend client.
+- */
+- txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG);
+- if (!txn) {
+- _log_err("Failed to create CONFIG Transaction for downloading CONFIGs");
+- return -1;
++void mgmt_txn_destroy(void)
++{
++ struct mgmt_txn *txn, *next;
++
++ TAILQ_FOREACH_SAFE (txn, &txn_txns, link, next) {
++ /* Free all txn_reqs associated with this txn */
++ while (!TAILQ_EMPTY(&txn->reqs))
++ txn_req_free(TAILQ_FIRST(&txn->reqs));
++ assert(txn->refcount == 1);
++ TXN_DECREF(txn);
+ }
+
+- _dbg("Created rollback txn-id: %" PRIu64, txn->txn_id);
+-
+- /*
+- * Set the changeset for transaction to commit and trigger the commit
+- * request.
+- */
+- txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG);
+- txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_CANDIDATE;
+- txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx;
+- txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING;
+- txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx;
+- txn_req->req.commit_cfg.validate_only = false;
+- txn_req->req.commit_cfg.abort = false;
+- txn_req->req.commit_cfg.rollback = true;
+- txn_req->req.commit_cfg.cmt_stats = &dummy_stats;
+- txn_req->req.commit_cfg.cfg_chgs = cfg_chgs;
+-
+- /*
+- * Trigger a COMMIT-CONFIG process.
+- */
+- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG);
+- return 0;
++ if (txn_id_tab)
++ hash_free(txn_id_tab);
+ }
+diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
+index f472d4f839..54ebbfd803 100644
+--- a/mgmtd/mgmt_txn.h
++++ b/mgmtd/mgmt_txn.h
+@@ -4,6 +4,7 @@
+ *
+ * Copyright (C) 2021 Vmware, Inc.
+ * Pushpasis Sarkar <spushpasis@vmware.com>
++ * Copyright (c) 2025, LabN Consulting, L.L.C.
+ */
+
+ #ifndef _FRR_MGMTD_TXN_H_
+@@ -27,10 +28,7 @@
+
+ #define MGMTD_TXN_ID_NONE 0
+
+-PREDECL_LIST(mgmt_txns);
+-
+ struct mgmt_master;
+-struct mgmt_edit_req;
+
+ enum mgmt_txn_type {
+ MGMTD_TXN_TYPE_NONE = 0,
+@@ -82,105 +80,59 @@ static inline int16_t errno_from_nb_error(enum nb_error ret)
+ }
+ }
+
++/* --------------------- */
++/* Gernal Txn Functions. */
++/* --------------------- */
+
+-/* Initialise transaction module. */
+-extern int mgmt_txn_init(struct mgmt_master *cm, struct event_loop *tm);
+-
+-/* Destroy the transaction module. */
++extern void mgmt_txn_init(void);
+ extern void mgmt_txn_destroy(void);
+
+-/*
+- * Check if configuration transaction is in progress.
+- *
+- * Returns:
+- * true if in-progress, false otherwise.
+- */
+-extern bool mgmt_config_txn_in_progress(void);
++extern enum mgmt_result nb_error_to_mgmt_result(enum nb_error error);
+
+-/**
+- * Get the session ID associated with the given ``txn-id``.
+- *
+- */
+-extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id);
++/* Dump transaction status to vty */
++extern void mgmt_txn_status_write(struct vty *vty);
+
+-/*
+- * Create transaction.
+- *
+- * session_id
+- * Session ID.
+- *
+- * type
+- * Transaction type (CONFIG/SHOW/NONE)
+- *
+- * Returns:
+- * transaction ID.
+- */
+-extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type);
++/* ---------------------------------------- */
++/* Txn API for Backend messages and events. */
++/* ---------------------------------------- */
+
+-/*
+- * Destroy transaction.
+- *
+- * txn_id
+- * Unique transaction identifier.
+- */
++extern bool mgmt_txn_config_in_progress(void);
++extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id);
++extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type);
+ extern void mgmt_destroy_txn(uint64_t *txn_id);
+
+-/*
+- * Send commit-config request to be processed later in transaction.
+- *
+- * txn_id
+- * Unique transaction identifier.
+- *
+- * req_id
+- * Unique transaction request identifier.
+- *
+- * src_ds_id
+- * Source datastore ID.
+- *
+- * src_ds_hndl
+- * Source Datastore handle.
+- *
+- * validate_only
+- * TRUE if commit request needs to be validated only, FALSE otherwise.
+- *
+- * abort
+- * TRUE if need to restore Src DS back to Dest DS, FALSE otherwise.
+- *
+- * implicit
+- * TRUE if the commit is implicit, FALSE otherwise.
+- *
+- * unlock
+- * pass back in the commit config reply
+- *
+- * edit
+- * Additional info when triggered from native edit request.
+- *
+- * Returns:
+- * 0 on success, -1 on failures.
+- */
+-extern int
++/**
++ * mgmt_txn_send_commit_config_req() - Send commit-config to apply the config changes.
++ * @txn_id: Unique transaction identifier.
++ * @req_id: Unique transaction request identifier.
++ * @src_ds_id: Source datastore ID.
++ * @src_ds_hndl: Source Datastore handle.
++ * @validate_only: TRUE if commit request needs to be validated only, FALSE otherwise.
++ * @abort: TRUE if need to restore Src DS back to Dest DS, FALSE otherwise.
++ * @implicit: TRUE if the commit is implicit, FALSE otherwise.
++ * @unlock: pass back in the commit config reply
++ * @edit: Additional info when triggered from native edit request.
++ */
++extern void
+ mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id,
+ struct mgmt_ds_ctx *dst_ds_ctx, enum mgmt_ds_id dst_ds_id,
+ struct mgmt_ds_ctx *src_ds_ctx, bool validate_only, bool abort,
+ bool implicit, bool unlock, struct mgmt_edit_req *edit);
+
+ /**
+- * Send get-tree to the backend `clients`.
+- *
+- * Args:
+- * txn_id: Transaction identifier.
+- * req_id: FE client request identifier.
+- * clients: Bitmask of clients to send get-tree to.
+- * ds_id: datastore ID.
+- * result_type: LYD_FORMAT result format.
+- * flags: option flags for the request.
+- * wd_options: LYD_PRINT_WD_* flags for the result.
+- * simple_xpath: true if xpath is simple (only key predicates).
+- * ylib: libyang tree for yang-library module to be merged.
+- * xpath: The xpath to get the tree from.
++ * mgmt_txn_send_get_tree() - Send get-tree to the backend `clients`.
++ * @txn_id: Transaction identifier.
++ * @req_id: FE client request identifier.
++ * @clients: Bitmask of clients to send get-tree to.
++ * @ds_id: datastore ID.
++ * @result_type: LYD_FORMAT result format.
++ * @flags: option flags for the request.
++ * @wd_options: LYD_PRINT_WD_* flags for the result.
++ * @simple_xpath: true if xpath is simple (only key predicates).
++ * @ylib: libyang tree for yang-library module to be merged.
++ * @xpath: The xpath to get the tree from.
+ *
+- * Return:
+- * 0 on success.
++ * Return 0 on success.
+ */
+ extern int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+ enum mgmt_ds_id ds_id, LYD_FORMAT result_type, uint8_t flags,
+@@ -188,47 +140,18 @@ extern int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t cli
+ const char *xpath);
+
+ /**
+- * Send edit request.
+- *
+- * Args:
+- * txn_id: Transaction identifier.
+- * req_id: FE client request identifier.
+- * ds_id: Datastore ID.
+- * ds_ctx: Datastore context.
+- * commit_ds_id: Commit datastore ID.
+- * commit_ds_ctx: Commit datastore context.
+- * unlock: Unlock datastores after the edit.
+- * commit: Commit the candidate datastore after the edit.
+- * request_type: LYD_FORMAT request type.
+- * flags: option flags for the request.
+- * operation: The operation to perform.
+- * xpath: The xpath of data node to edit.
+- * data: The data tree.
+- */
+-extern int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id ds_id,
+- struct mgmt_ds_ctx *ds_ctx, enum mgmt_ds_id commit_ds_id,
+- struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit,
+- LYD_FORMAT request_type, uint8_t flags, uint8_t operation,
+- const char *xpath, const char *data);
+-
+-/**
+- * Send RPC request.
+- *
+- * Args:
+- * txn_id: Transaction identifier.
+- * req_id: FE client request identifier.
+- * clients: Bitmask of clients to send RPC to.
+- * result_type: LYD_FORMAT result format.
+- * xpath: The xpath of the RPC.
+- * data: The input parameters data tree.
+- * data_len: The length of the input parameters data.
+- *
+- * Return:
+- * 0 on success.
++ * mgmt_txn_send_rpc() - Send RPC request.
++ * @txn_id: Transaction identifier.
++ * @req_id: FE client request identifier.
++ * @clients: Bitmask of clients to send RPC to.
++ * @result_type: LYD_FORMAT result format.
++ * @xpath: The xpath of the RPC.
++ * @data: The input parameters data tree.
++ * @data_len: The length of the input parameters data.
+ */
+-extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+- LYD_FORMAT result_type, const char *xpath,
+- const char *data, size_t data_len);
++extern void mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
++ LYD_FORMAT result_type, const char *xpath, const char *data,
++ size_t data_len);
+
+ /**
+ * mgmt_txn_send_notify_selectors() - Send NOTIFY SELECT request.
+@@ -238,99 +161,32 @@ extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
+ * back to the given session.
+ * @clients: Bitmask of backend clients to send message to.
+ * @selectors: Array of selectors or NULL to resend all selectors to BE clients.
+- *
+- * Returns 0 on success.
+ */
+-extern int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients,
+- const char **selectors);
++extern void mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients,
++ const char **selectors);
+
+-/*
+- * Notifiy backend adapter on connection.
+- */
+-extern int
+-mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter,
+- bool connect);
+-
+-/*
+- * Reply to backend adapter about transaction create/delete.
+- */
+-extern int
+-mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success,
+- struct mgmt_be_client_adapter *adapter);
+
+ /*
+- * Reply to backend adapater with config data create request.
+- */
+-extern int mgmt_txn_notify_be_cfg_reply(uint64_t txn_id, bool success, const char *error_if_any,
+- struct mgmt_be_client_adapter *adapter);
+-
+-/*
+- * Reply to backend adapater with config data validate request.
+- */
+-extern int mgmt_txn_notify_be_cfg_validate_reply(
+- uint64_t txn_id, bool success, uint64_t batch_ids[],
+- size_t num_batch_ids, char *error_if_any,
+- struct mgmt_be_client_adapter *adapter);
+-
+-/*
+- * Reply to backend adapater with config data apply request.
+- */
+-extern int
+-mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success,
+- char *error_if_any,
+- struct mgmt_be_client_adapter *adapter);
+-
+-
+-/**
+- * Process a reply from a backend client to our get-tree request
+- *
+- * Args:
+- * adapter: The adapter that received the result.
+- * txn_id: The transaction for this get-tree request.
+- * req_id: The request ID for this transaction.
+- * error: the integer error value (negative)
+- * errstr: the string description of the error.
+- */
+-int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter,
+- uint64_t txn_id, uint64_t req_id, int error,
+- const char *errstr);
+-
+-/**
+- * Process a reply from a backend client to our get-tree request
++ * Trigger rollback config apply.
+ *
+- * Args:
+- * adapter: The adapter that received the result.
+- * data_msg: The message from the backend.
+- * msg_len: Total length of the message.
++ * Creates a new transaction and commit request for rollback.
+ */
++extern int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx,
++ struct mgmt_ds_ctx *dst_ds_ctx);
+
+-extern int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter,
+- struct mgmt_msg_tree_data *data_msg,
+- size_t msg_len);
++/* ---------------------------------------- */
++/* Txn API for Backend messages and events. */
++/* ---------------------------------------- */
+
+-/**
+- * Process a reply from a backend client to our RPC request
+- *
+- * Args:
+- * adapter: The adapter that received the result.
+- * reply_msg: The message from the backend.
+- * msg_len: Total length of the message.
+- */
+-extern int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter,
+- struct mgmt_msg_rpc_reply *reply_msg,
+- size_t msg_len);
++extern void mgmt_txn_handle_cfg_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter);
++extern void mgmt_txn_handle_cfg_apply_reply(uint64_t txn_id,
++ struct mgmt_be_client_adapter *adapter);
++extern void mgmt_txn_handle_error_reply(struct mgmt_be_client_adapter *adapter, uint64_t txn_id,
++ uint64_t req_id, int error, const char *errstr);
++extern int mgmt_txn_handle_be_adapter_connect(struct mgmt_be_client_adapter *adapter, bool connect);
++extern void mgmt_txn_handle_rpc_reply(struct mgmt_be_client_adapter *adapter,
++ struct mgmt_msg_rpc_reply *reply_msg, size_t msg_len);
++extern void mgmt_txn_handle_tree_data_reply(struct mgmt_be_client_adapter *adapter,
++ struct mgmt_msg_tree_data *data_msg, size_t msg_len);
+
+-/*
+- * Dump transaction status to vty.
+- */
+-extern void mgmt_txn_status_write(struct vty *vty);
+-
+-/*
+- * Trigger rollback config apply.
+- *
+- * Creates a new transaction and commit request for rollback.
+- */
+-extern int
+-mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx,
+- struct mgmt_ds_ctx *dst_ds_ctx);
+ #endif /* _FRR_MGMTD_TXN_H_ */
+diff --git a/mgmtd/mgmt_txn_cfg.c b/mgmtd/mgmt_txn_cfg.c
+new file mode 100644
+index 0000000000..742c03cc4c
+--- /dev/null
++++ b/mgmtd/mgmt_txn_cfg.c
+@@ -0,0 +1,1096 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * November 18 2025, Christian Hopps <chopps@labn.net>
++ *
++ * Copyright (c) 2025, LabN Consulting, L.L.C.
++ *
++ */
++
++#include <zebra.h>
++#include "mgmtd/mgmt_txn_priv.h"
++
++/* ================================ */
++/* Config Request Types and Globals */
++/* ================================ */
++
++enum mgmt_commit_phase {
++ MGMTD_COMMIT_PHASE_SEND_CFG = 0,
++ MGMTD_COMMIT_PHASE_APPLY_CFG,
++ MGMTD_COMMIT_PHASE_FINISH,
++};
++
++static const char *const mgmt_commit_phase_name[] = {
++ [MGMTD_COMMIT_PHASE_SEND_CFG] = "SEND-CFG",
++ [MGMTD_COMMIT_PHASE_APPLY_CFG] = "APPLY-CFG",
++ [MGMTD_COMMIT_PHASE_FINISH] = "FINISH",
++};
++
++struct txn_req_commit {
++ struct txn_req req;
++ enum mgmt_ds_id src_ds_id;
++ struct mgmt_ds_ctx *src_ds_ctx;
++ enum mgmt_ds_id dst_ds_id;
++ struct mgmt_ds_ctx *dst_ds_ctx;
++
++ uint32_t nb_txn_id;
++
++ uint8_t validate_only : 1;
++ uint8_t abort : 1;
++ uint8_t implicit : 1;
++ uint8_t rollback : 1;
++ uint8_t init : 1;
++ uint8_t unlock_info : 1;
++ uint8_t txn_lock : 1;
++
++ /* Track commit phases */
++ enum mgmt_commit_phase phase;
++
++ /*
++ * Additional information when the commit is triggered by native edit
++ * request.
++ */
++ struct mgmt_edit_req *edit;
++
++
++ /* Used for holding changes mgmtd itself is interested in */
++ struct nb_transaction *mgmtd_nb_txn;
++
++ /*
++ * Details on all the Backend Clients associated with
++ * this commit.
++ */
++ uint64_t clients; /* interested clients */
++ uint64_t clients_wait; /* set when cfg_req sent */
++
++ struct mgmt_commit_stats *cmt_stats;
++};
++#define as_commit(txn_req) \
++ ({ \
++ assert((txn_req)->req_type == TXN_REQ_TYPE_COMMIT); \
++ (struct txn_req_commit *)(txn_req); \
++ })
++
++/* The number of init txn's in progress, we lock on 0->1 and unlock on 1->0 */
++uint txn_init_readers;
++
++static void txn_cfg_timeout(struct event *event);
++static void txn_cfg_adapter_acked(struct txn_req_commit *ccreq,
++ struct mgmt_be_client_adapter *adapter);
++static int txn_cfg_send_txn_delete(struct mgmt_be_client_adapter *adapter, uint64_t txn_id);
++
++/* ------------------------------------- */
++/* Utility Functions for Commit Requests */
++/* ------------------------------------- */
++
++static inline struct txn_req_commit *txn_txn_req_commit(struct mgmt_txn *txn)
++{
++ struct txn_req *first_req = TAILQ_FIRST(&txn->reqs);
++
++ if (first_req && first_req->req_type == TXN_REQ_TYPE_COMMIT)
++ return as_commit(first_req);
++ return NULL;
++}
++
++static int txn_set_config_error(struct txn_req *txn_req, enum mgmt_result error,
++ const char *error_info)
++{
++ txn_req->error = error;
++ darr_in_strdup(txn_req->err_info, error_info);
++ return -1;
++}
++
++/* ------------------------------------ */
++/* Allocate and Cleanup Commit Requests */
++/* ------------------------------------ */
++
++static struct txn_req_commit *txn_req_commit_alloc(struct mgmt_txn *txn, uint64_t req_id)
++{
++ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_COMMIT,
++ sizeof(struct txn_req_commit));
++ assert(txn->type == MGMTD_TXN_TYPE_CONFIG);
++ return as_commit(txn_req);
++}
++
++void txn_cfg_cleanup(struct txn_req *txn_req)
++{
++ struct txn_req_commit *ccreq = as_commit(txn_req);
++ struct mgmt_be_client_adapter *adapter;
++ enum mgmt_commit_phase phase;
++ enum mgmt_be_client_id id;
++ uint64_t txn_id = txn_req->txn->txn_id;
++ uint64_t clients;
++
++ _dbg("Deleting COMMITCFG req-id: %Lu txn-id: %Lu", txn_req->req_id, txn_id);
++
++ XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit);
++
++ /* If we (still) had an internal nb transaction, abort it */
++ if (ccreq->mgmtd_nb_txn) {
++ nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0);
++ ccreq->mgmtd_nb_txn = NULL;
++ }
++
++ /*
++ * What state are we in:
++ *
++ * FINISH: Everythign went OK. If this was NOT validate-only then all
++ * clients have replied to the CFG_APPLY and deleted their txn state. We
++ * have nothing to do. Otherwise, this is validate-only and all the
++ * clients still have config txn state with the changes. We need to
++ * clean that txn state up. Treat the phase as SEND-CFG so we send
++ * config abort (TXN-delete) to have the clients delete their txn state.
++ *
++ * APPLY: Any clients that have not CFG_APPLY_REPLY'd yet (clients_wait)
++ * need to be disconnected so they will resync config state on reconnect.
++ *
++ * SEND-CFG: Some clients may have cfg txn state. We send all clients
++ * config abort (TXN-delete) to cleanup any transaction state. Even if
++ * clients have rejected the config and deleted txn state already they
++ * are expected to handle receving TXN-delete gracefully (e.g., their
++ * reply may still be in flight when we got here).
++ */
++ phase = ccreq->phase;
++ if (phase == MGMTD_COMMIT_PHASE_FINISH && ccreq->validate_only)
++ phase = MGMTD_COMMIT_PHASE_SEND_CFG;
++
++ switch (phase) {
++ case MGMTD_COMMIT_PHASE_FINISH:
++ break;
++ case MGMTD_COMMIT_PHASE_APPLY_CFG:
++ /* cache and clear the bits as they are checked on client disconnect */
++ clients = ccreq->clients_wait;
++ ccreq->clients_wait = 0;
++ ccreq->clients = 0;
++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) {
++ _dbg("Disconnect client: %s for txn-id: %Lu resync required",
++ adapter->name, txn_id);
++ msg_conn_disconnect(adapter->conn, false);
++ }
++ break;
++ case MGMTD_COMMIT_PHASE_SEND_CFG:
++ /* cache and clear the bits as they are checked on client disconnect */
++ clients = ccreq->clients;
++ ccreq->clients_wait = 0;
++ ccreq->clients = 0;
++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) {
++ if (!txn_cfg_send_txn_delete(adapter, txn_id))
++ continue;
++ _dbg("Disconnect client: %s for txn-id: %Lu failed to send CFG_ABORT",
++ adapter->name, txn_id);
++ msg_conn_disconnect(adapter->conn, false);
++ }
++ break;
++ }
++
++ /*
++ * Now release the TXN locks if held.
++ */
++ if (ccreq->txn_lock) {
++ mgmt_ds_txn_unlock(ccreq->src_ds_ctx, txn_id);
++ mgmt_ds_txn_unlock(ccreq->dst_ds_ctx, txn_id);
++ }
++}
++
++/* =============================================== */
++/* Config Phases For Syncing with Backend Clients */
++/* =============================================== */
++
++/* --------- */
++/* Any Phase */
++/* --------- */
++
++/*
++ * Abort and Cleanup Config Changes.
++ *
++ * This will cleanup the Config Txn on the backend client. Backends that have
++ * replied to SEND_CFG with an error (validation/prepare) have already cleaned
++ * up. Likewise Backends that have replied to APPLY_CFG with an success
++ * have also already done this. In any case Backends must handle receiving this
++ * message even if they've already cleaned up.
++ */
++static int txn_cfg_send_txn_delete(struct mgmt_be_client_adapter *adapter, uint64_t txn_id)
++{
++ struct mgmt_msg_txn_req *msg;
++ int ret;
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_req, 0, MTYPE_MSG_NATIVE_TXN_REQ);
++ msg->code = MGMT_MSG_CODE_TXN_REQ;
++ msg->refer_id = txn_id;
++ msg->create = false;
++
++ ret = mgmt_be_adapter_send(adapter, msg);
++ mgmt_msg_native_free_msg(msg);
++ return ret;
++}
++
++
++/* -------------- */
++/* SEND_CFG phase */
++/* -------------- */
++
++/*
++ * This is the real workhorse. Take the list of changes and check each change
++ * against our backend clients to see who is interested. For each interested
++ * client we create a config message -- we also track which changes mgmtd itself
++ * is interested in. If a client is interested we add the change to it's config
++ * message and track the type of chagne in another string array.
++ *
++ * When done with the changes we first handle mgmtd's own changes, validating,
++ * and preparing them into the mgmtd candidate (using normal lib/northbound
++ * routines). Then for each backend client we complete it's config message by
++ * append the action string and then we send it to the client.
++ *
++ * Return: 0 on success, the caller should arrange to receive REPLYS from the
++ * clients before proceeding further, if no clients were interested the caller
++ * should proceed to apply the mgmtd local changes (if any) to complete the txn.
++ *
++ * On failure, if we were called from a front-end client (the TXN has a
++ * session_id) we have it reply with the error and cleanup the txn. Otherwise
++ * this is an internal txn and we just cleanup the txn in that case. In either
++ * case we return -1 and the caller should not proceed further.
++ *
++ * Can disconnect backends, but this is not called from backends, so it's safe.
++ */
++
++static int txn_cfg_make_and_send_cfg_req(struct txn_req_commit *ccreq,
++ const struct nb_config_cbs *changes,
++ uint64_t init_client_mask)
++{
++ struct nb_config_cb *cb, *nxt;
++ struct nb_config_change *chg;
++ struct nb_config_cbs mgmtd_changes = { 0 };
++ char *xpath = NULL, *value = NULL;
++ enum mgmt_be_client_id id;
++ struct mgmt_be_client_adapter *adapter;
++ struct txn_req *txn_req = &ccreq->req;
++ struct mgmt_msg_cfg_req **cfg_msgs = NULL;
++ char **cfg_actions = NULL;
++ uint64_t *num_cfg_data = NULL;
++ bool mgmtd_interest;
++ uint batch_items = 0;
++ uint num_chgs = 0;
++ uint64_t clients, chg_clients;
++ char op;
++ int ret = -1;
++
++ RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) {
++ chg = (struct nb_config_change *)cb;
++
++ xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0);
++ assert(xpath);
++
++ value = (char *)lyd_get_value(chg->cb.dnode);
++ if (!value)
++ value = (char *)MGMTD_BE_CONTAINER_NODE_VAL;
++
++ _dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL");
++
++ /* Collect changes for mgmtd itself */
++ mgmtd_interest = false;
++ if (!init_client_mask && mgmt_is_mgmtd_interested(xpath) &&
++ /* We send tree changes to BEs that we don't need callbacks for */
++ nb_cb_operation_is_valid(cb->operation, cb->dnode->schema)) {
++ uint32_t seq = cb->seq;
++
++ nb_config_diff_add_change(&mgmtd_changes, cb->operation, &seq, cb->dnode);
++ mgmtd_interest = true;
++ }
++ if (init_client_mask)
++ clients = init_client_mask;
++ else
++ clients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG);
++ if (!clients)
++ _dbg("No backends interested in xpath: %s", xpath);
++
++ if (mgmtd_interest || clients)
++ num_chgs++;
++
++ chg_clients = 0;
++ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) {
++ SET_IDBIT(chg_clients, id);
++
++ darr_ensure_i(cfg_msgs, id);
++ darr_ensure_i(cfg_actions, id);
++ if (!cfg_msgs[id]) {
++ /* Allocate a new config message */
++ struct mgmt_msg_cfg_req *msg;
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_req, 0,
++ MTYPE_MSG_NATIVE_CFG_REQ);
++ msg->code = MGMT_MSG_CODE_CFG_REQ;
++ msg->refer_id = txn_req->txn->txn_id;
++ msg->req_id = txn_req->req_id;
++ cfg_msgs[id] = msg;
++ }
++
++ /*
++ * On the backend, we don't really care if it's CREATE
++ * or MODIFY, because the existence was already checked
++ * on the frontend. Therefore we use SET for both.
++ */
++ op = chg->cb.operation == NB_CB_DESTROY ? 'd' : 'm';
++ darr_push(cfg_actions[id], op);
++
++ mgmt_msg_native_add_str(cfg_msgs[id], xpath);
++ if (op == 'm')
++ mgmt_msg_native_add_str(cfg_msgs[id], value);
++ if (DEBUG_MODE_CHECK(&mgmt_debug_txn, DEBUG_MODE_ALL)) {
++ darr_ensure_i(num_cfg_data, id);
++ num_cfg_data[id]++;
++ _dbg(" -- %s item: %Lu", adapter->name, num_cfg_data[id]);
++ }
++
++ batch_items++;
++ }
++
++ if (clients && clients != chg_clients)
++ _dbg("Some deamons interested in XPATH are not currently connected");
++
++ ccreq->clients |= chg_clients;
++
++ free(xpath);
++ }
++ _dbg("Total xpath changes processed: %u sent", num_chgs);
++
++ ccreq->cmt_stats->last_batch_cnt = batch_items;
++
++ if (!RB_EMPTY(nb_config_cbs, &mgmtd_changes)) {
++ /* Create a northbound transaction for local mgmtd config changes */
++ char errmsg[BUFSIZ] = { 0 };
++ size_t errmsg_len = sizeof(errmsg);
++ struct nb_context nb_ctx = { 0 };
++
++ _dbg("Processing mgmtd bound changes");
++
++ assert(!ccreq->mgmtd_nb_txn);
++ nb_ctx.client = NB_CLIENT_MGMTD_SERVER;
++
++ /* Prepare the mgmtd local config changes */
++ /*
++ * This isn't calling the VALIDATE callback, it's just
++ * running PREPARE. See #19948
++ */
++ if (nb_changes_commit_prepare(nb_ctx, mgmtd_changes, "mgmtd-changes", NULL,
++ &ccreq->mgmtd_nb_txn, errmsg, errmsg_len)) {
++ _log_err("Failed to prepare local config for mgmtd: %s", errmsg);
++ if (ccreq->mgmtd_nb_txn) {
++ nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0);
++ ccreq->mgmtd_nb_txn = NULL;
++ }
++ (void)txn_set_config_error(txn_req, MGMTD_INTERNAL_ERROR,
++ "Failed to prepare local config for mgmtd");
++ goto done;
++ }
++ assert(ccreq->mgmtd_nb_txn);
++ }
++
++ /* Record txn create start time */
++ if (mm->perf_stats_en)
++ gettimeofday(&ccreq->cmt_stats->txn_create_start, NULL);
++
++ /* Send the messages to the backends */
++ FOREACH_BE_ADAPTER_BITS (id, adapter, ccreq->clients) {
++ /* NUL terminate actions string and add to tail of message */
++ darr_ensure_i(cfg_actions, id); /* required for clang SA NULL ptr check */
++ darr_push(cfg_actions[id], 0);
++ mgmt_msg_native_add_str(cfg_msgs[id], cfg_actions[id]);
++ _dbg("Finished CFG_REQ for '%s' txn-id: %Lu with actions: %s", adapter->name,
++ txn_req->txn->txn_id, cfg_actions[id]);
++
++ if (mgmt_be_adapter_send(adapter, cfg_msgs[id])) {
++ /* remove this client and reset the connection */
++ UNSET_IDBIT(ccreq->clients, id);
++ msg_conn_disconnect(adapter->conn, false);
++ }
++ ccreq->cmt_stats->last_num_cfgdata_reqs++;
++ }
++ /* Record who we are waiting for */
++ ccreq->clients_wait = ccreq->clients;
++
++ if (ccreq->clients) {
++ /* set a timeout for hearing back from the backend clients */
++ event_add_timer(mm->master, txn_cfg_timeout, txn_req,
++ MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC, &txn_req->timeout);
++ } else {
++ /* We have no connected interested clients */
++ if (ccreq->mgmtd_nb_txn)
++ _dbg("No connected and interested backend clients, proceed with mgmtd local changes");
++ else
++ _dbg("No connected and interested backend clients, proceed to apply changes");
++ txn_cfg_adapter_acked(ccreq, NULL);
++ }
++
++ ret = 0;
++done:
++ darr_free(num_cfg_data);
++ darr_free_func(cfg_msgs, mgmt_msg_native_free_msg);
++ darr_free_free(cfg_actions);
++ return ret;
++}
++
++/* --------------- */
++/* APPLY_CFG phase */
++/* --------------- */
++
++/*
++ * Send CFG_APPLY_REQs to all the backend client.
++ */
++static void txn_cfg_send_cfg_apply(struct txn_req_commit *ccreq)
++{
++ enum mgmt_be_client_id id;
++ struct mgmt_be_client_adapter *adapter;
++ struct mgmt_msg_cfg_apply_req *msg;
++ uint64_t txn_id = ccreq->req.txn->txn_id;
++
++ assert(ccreq->req.txn->type == MGMTD_TXN_TYPE_CONFIG);
++
++ assert(!ccreq->validate_only);
++
++ if (mm->perf_stats_en)
++ gettimeofday(&ccreq->cmt_stats->apply_cfg_start, NULL);
++ /*
++ * Handle mgmtd internal special case
++ */
++ if (ccreq->mgmtd_nb_txn) {
++ char errmsg[BUFSIZ] = { 0 };
++
++ _dbg("Applying mgmtd local bound changes");
++
++ (void)nb_candidate_commit_apply(ccreq->mgmtd_nb_txn, false, NULL, errmsg,
++ sizeof(errmsg));
++ ccreq->mgmtd_nb_txn = NULL;
++ }
++
++ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_apply_req, 0,
++ MTYPE_MSG_NATIVE_CFG_APPLY_REQ);
++ msg->code = MGMT_MSG_CODE_CFG_APPLY_REQ;
++ msg->refer_id = txn_id;
++ FOREACH_BE_ADAPTER_BITS (id, adapter, ccreq->clients) {
++ if (mgmt_be_adapter_send(adapter, msg)) {
++ msg_conn_disconnect(adapter->conn, false);
++ continue;
++ }
++ SET_IDBIT(ccreq->clients_wait, id);
++ ccreq->cmt_stats->last_num_apply_reqs++;
++ }
++ mgmt_msg_native_free_msg(msg);
++
++ if (!ccreq->clients_wait) {
++ _dbg("No backends to wait for on CFG_APPLY for txn-id: %Lu", txn_id);
++ txn_cfg_adapter_acked(ccreq, NULL);
++ }
++}
++
++
++/* ------------ */
++/* FINISH phase */
++/* ------------ */
++
++/*
++ * Finish processing a commit-config request.
++ *
++ * NOTE: can disconnect (and delete) backends.
++ */
++static void txn_finish_commit(struct txn_req_commit *ccreq, enum mgmt_result result,
++ const char *error_if_any)
++{
++ bool success, apply_op, accept_changes, discard_changes;
++ struct txn_req *txn_req = as_txn_req(ccreq);
++ struct mgmt_txn *txn = txn_req->txn;
++ int ret = 0;
++
++ success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES);
++
++ /*
++ * Stop the commit-timeout timer.
++ */
++ event_cancel(&txn_req->timeout);
++
++ /*
++ * Decide what our datastores should now look like
++ *
++ * Accept changes into running (candidate->running):
++ *
++ * If this is a commit (apply or rollback) and we've at least started
++ * telling backend clients to apply, we need to accept the changes
++ * into running. Any clients who have not acked the apply yet will be
++ * disconnected in the txn cleanup so they sync to running on
++ * reconnect.
++ *
++ * Discard candidate changes (running->candidate):
++ *
++ * If this is a successful abort
++ */
++ apply_op = !ccreq->validate_only && !ccreq->abort && !ccreq->init;
++ accept_changes = ccreq->phase >= MGMTD_COMMIT_PHASE_APPLY_CFG && apply_op;
++ discard_changes = (result == MGMTD_SUCCESS && ccreq->abort);
++ if (accept_changes) {
++ bool create_cmt_info_rec = (result != MGMTD_NO_CFG_CHANGES && !ccreq->rollback);
++
++ mgmt_ds_copy_dss(ccreq->dst_ds_ctx, ccreq->src_ds_ctx, create_cmt_info_rec);
++ }
++ if (discard_changes)
++ mgmt_ds_copy_dss(ccreq->src_ds_ctx, ccreq->dst_ds_ctx, false);
++
++ /*
++ * For internal txns do lock cleanup, for front-end session send replies.
++ */
++ if (ccreq->init) {
++ /*
++ * This is the backend init request. Unlock the running
++ * datastore if we are the last reader.
++ */
++ if (!--txn_init_readers)
++ mgmt_ds_unlock(ccreq->dst_ds_ctx, 0);
++ TXN_DECREF(txn);
++ } else if (ccreq->rollback) {
++ /*
++ * Resume processing the rollback command.
++ *
++ * TODO: there's no good reason to special case rollback, the
++ * rollback boolean should be passed back to the FE client and it
++ * can do the right thing.
++ */
++ mgmt_history_rollback_complete(success);
++ TXN_DECREF(txn);
++ } else if (!ccreq->edit) {
++ /*
++ * This means we are in the mgmtd CLI vty code -- session code
++ * will release it's reference on the TXN
++ */
++ ret = mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, ccreq->src_ds_id,
++ ccreq->dst_ds_id, txn_req->req_id,
++ ccreq->validate_only, ccreq->unlock_info,
++ result, error_if_any);
++ } else {
++ /* Session code will release it's reference on the TXN */
++ ret = mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id,
++ txn_req->req_id, ccreq->unlock_info,
++ true /* commit */, &ccreq->edit,
++ success ? 0 : MGMTD_INTERNAL_ERROR,
++ error_if_any);
++ }
++ if (ret)
++ _log_err("Failed sending config reply for txn-id: %Lu session-id: %Lu",
++ txn->txn_id, txn->session_id);
++
++ ccreq->cmt_stats = NULL;
++ txn_req_free(txn_req);
++}
++
++/* ---------------------- */
++/* Config Phase Machinery */
++/* ---------------------- */
++
++static void txn_cfg_next_phase(struct txn_req_commit *ccreq)
++{
++ struct txn_req *txn_req = &ccreq->req;
++
++ switch (ccreq->phase) {
++ case MGMTD_COMMIT_PHASE_SEND_CFG:
++ if (ccreq->validate_only)
++ ccreq->phase = MGMTD_COMMIT_PHASE_FINISH;
++ else
++ ccreq->phase = MGMTD_COMMIT_PHASE_APPLY_CFG;
++ break;
++ case MGMTD_COMMIT_PHASE_APPLY_CFG:
++ if (mm->perf_stats_en)
++ gettimeofday(&ccreq->cmt_stats->apply_cfg_end, NULL);
++ ccreq->phase = MGMTD_COMMIT_PHASE_FINISH;
++ break;
++ case MGMTD_COMMIT_PHASE_FINISH:
++ default:
++ assert(!"Invalid commit phase transition from FINISH");
++ break;
++ }
++
++ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu transition to state: %s", txn_req->txn->txn_id,
++ mgmt_commit_phase_name[ccreq->phase]);
++
++ switch (ccreq->phase) {
++ case MGMTD_COMMIT_PHASE_APPLY_CFG:
++ txn_cfg_send_cfg_apply(ccreq);
++ break;
++ case MGMTD_COMMIT_PHASE_FINISH:
++ if (mm->perf_stats_en)
++ gettimeofday(&ccreq->cmt_stats->txn_del_start, NULL);
++ txn_finish_commit(ccreq, MGMTD_SUCCESS, NULL);
++ return;
++ case MGMTD_COMMIT_PHASE_SEND_CFG:
++ default:
++ assert(!"Invalid commit phase transition to SEND_CFG");
++ }
++}
++
++static void txn_cfg_adapter_acked(struct txn_req_commit *ccreq,
++ struct mgmt_be_client_adapter *adapter)
++{
++ struct txn_req *txn_req = &ccreq->req;
++
++ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu in state: %s", txn_req->txn->txn_id,
++ mgmt_commit_phase_name[ccreq->phase]);
++
++ if (adapter) {
++ enum mgmt_be_client_id id = adapter->id;
++
++ if (IS_IDBIT_SET(ccreq->clients_wait, id))
++ UNSET_IDBIT(ccreq->clients_wait, id);
++ else
++ _dbg("Wasn't waiting on client: %s", adapter->name);
++ }
++
++ if (ccreq->clients_wait) {
++ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu still waiting on clients: 0x%Lx",
++ txn_req->txn->txn_id, ccreq->clients_wait);
++ return;
++ }
++
++ txn_cfg_next_phase(ccreq);
++}
++
++static void txn_cfg_timeout(struct event *event)
++{
++ struct txn_req *txn_req;
++ struct txn_req_commit *ccreq;
++
++ txn_req = EVENT_ARG(event);
++ ccreq = as_commit(txn_req);
++
++ /*
++ * If we are applying changes we need to return SUCCESS as there is no
++ * way to abort those. Slow backends that haven't replied yet will be
++ * disconnected. If we are still sending config for validate/prepare
++ * phase we return an error and abort the commit.
++ */
++ if (ccreq->phase < MGMTD_COMMIT_PHASE_APPLY_CFG) {
++ _log_err("Backend timeout validating txn-id: %Lu waiting: 0x%Lx aborting commit",
++ txn_req->txn->txn_id, ccreq->clients_wait);
++ txn_finish_commit(ccreq, MGMTD_INTERNAL_ERROR,
++ "Some backend clients taking too long to validate the changes.");
++ } else {
++ _log_warn("Backend timeout applying txn-id: %Lu waiting: 0x%Lx, applying commit",
++ txn_req->txn->txn_id, ccreq->clients_wait);
++ txn_finish_commit(ccreq, MGMTD_SUCCESS,
++ "Some backend clients taking too long to apply the changes.");
++ }
++}
++
++/*
++ * Given a list of config changes, make backend client messages and send them. Aslo
++ * apply any mgmtd specific config as well.
++ */
++static int txn_cfg_send_config_changes(struct txn_req_commit *ccreq,
++ struct nb_config_cbs *cfg_chgs, uint64_t init_client_mask)
++{
++ int ret;
++
++ if (mm->perf_stats_en)
++ gettimeofday(&ccreq->cmt_stats->prep_cfg_start, NULL);
++
++ ret = txn_cfg_make_and_send_cfg_req(ccreq, cfg_chgs, init_client_mask);
++ nb_config_diff_del_changes(cfg_chgs);
++ return ret;
++}
++
++/* ================================== */
++/* APIs for Backend Clients (daemons) */
++/* ================================== */
++
++/* ------------------------------------ */
++/* Handle Messages From Backend Clients */
++/* ------------------------------------ */
++
++static struct txn_req_commit *
++txn_cfg_ensure_msg(uint64_t txn_id, struct mgmt_be_client_adapter *adapter, const char *tag)
++{
++ struct mgmt_txn *txn = txn_lookup(txn_id);
++ struct txn_req_commit *ccreq;
++
++ if (!txn) {
++ _log_err("%s reply from '%s' for txn-id: %Lu no TXN, resetting connection", tag,
++ adapter->name, txn_id);
++ return NULL;
++ }
++ if (txn->type != MGMTD_TXN_TYPE_CONFIG) {
++ _log_err("%s reply from '%s' for txn-id: %Lu failed wrong txn TYPE %u, resetting connection",
++ tag, adapter->name, txn_id, txn->type);
++ return NULL;
++ }
++ ccreq = txn_txn_req_commit(txn);
++ if (!ccreq) {
++ _log_err("%s reply from '%s' for txn-id: %Lu failed no COMMITCFG_REQ, resetting connection",
++ tag, adapter->name, txn_id);
++ return NULL;
++ }
++ /* make sure we are part of this config commit */
++ if (!IS_IDBIT_SET(ccreq->clients, adapter->id)) {
++ _log_err("%s reply from '%s' for txn-id %Lu not participating, resetting connection",
++ tag, adapter->name, txn_id);
++ return NULL;
++ }
++ return ccreq;
++}
++
++/*
++ * Handle CFG_REQ reply from backend client adapter. This is the only point of
++ * failure that is expected in the config commit process; it is where the
++ * backend client can report validation or prepare errors.
++ *
++ * NOTE: can disconnect (and delete) the backend.
++ */
++static void txn_cfg_handle_cfg_reply(struct txn_req_commit *ccreq, bool success,
++ const char *error_if_any,
++ struct mgmt_be_client_adapter *adapter)
++{
++ struct txn_req *txn_req = as_txn_req(ccreq);
++
++ if (!IS_IDBIT_SET(ccreq->clients_wait, adapter->id)) {
++ _log_warn("CFG_REPLY from '%s' but not waiting for it txn-id: %Lu, resetting conn",
++ adapter->name, txn_req->txn->txn_id);
++ msg_conn_disconnect(adapter->conn, false);
++ return;
++ }
++
++ if (success)
++ _dbg("CFG_REPLY from '%s'", adapter->name);
++ else {
++ _log_err("CFG_REQ to '%s' failed err: %s", adapter->name, error_if_any ?: "None");
++ txn_finish_commit(ccreq, MGMTD_VALIDATION_ERROR,
++ error_if_any ?: "config validation failed by backend daemon");
++ return;
++ }
++
++ txn_cfg_adapter_acked(ccreq, adapter);
++}
++
++/*
++ * NOTE: can disconnect (and delete) the backend.
++ */
++void mgmt_txn_handle_cfg_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter)
++{
++ struct txn_req_commit *ccreq = txn_cfg_ensure_msg(txn_id, adapter, "CFG_REQ");
++
++ if (!ccreq)
++ msg_conn_disconnect(adapter->conn, false);
++ else
++ txn_cfg_handle_cfg_reply(ccreq, true, NULL, adapter);
++}
++
++/*
++ * NOTE: can disconnect (and delete) the backend.
++ */
++void mgmt_txn_handle_cfg_apply_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter)
++{
++ struct txn_req_commit *ccreq = txn_cfg_ensure_msg(txn_id, adapter, "CFG_APPLY");
++
++ if (!ccreq)
++ msg_conn_disconnect(adapter->conn, false);
++ else
++ txn_cfg_adapter_acked(ccreq, adapter);
++}
++
++/*
++ * NOTE: can disconnect (and delete) the backend.
++ */
++void txn_cfg_handle_error(struct txn_req *txn_req, struct mgmt_be_client_adapter *adapter,
++ int error, const char *errstr)
++{
++ struct txn_req_commit *ccreq;
++
++ /*
++ * Handle an error during a configuration transaction.
++ */
++ ccreq = txn_cfg_ensure_msg(txn_req->txn->txn_id, adapter, "ERROR");
++ if (!ccreq || as_txn_req(ccreq) != txn_req) {
++ msg_conn_disconnect(adapter->conn, false);
++ return;
++ }
++ /*
++ * We only handle errors in reply to our CFG_REQ messages (due
++ * to validation/preparation). Otherwise, this is an error
++ * during some other phase of the commit process and we
++ * disconnect the client and start over.
++ */
++ if (ccreq->phase == MGMTD_COMMIT_PHASE_SEND_CFG)
++ txn_cfg_handle_cfg_reply(ccreq, false, errstr, adapter);
++ else
++ /* Drop the connection these errors should never happen */
++ msg_conn_disconnect(adapter->conn, false);
++}
++
++/* ----------------------------------- */
++/* Backend Client Connect / Disconnect */
++/* ----------------------------------- */
++
++int txn_cfg_be_client_connect(struct mgmt_be_client_adapter *adapter)
++{
++ struct nb_config_cbs adapter_cfgs;
++ struct mgmt_txn *txn;
++ struct txn_req_commit *ccreq;
++ struct mgmt_ds_ctx *ds_ctx;
++
++ ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
++ assert(ds_ctx);
++
++ /* We don't send configs to ourselves we already have them */
++ if (adapter->id == MGMTD_BE_CLIENT_ID_MGMTD) {
++ _dbg("Not sending initial config to myself");
++ return 0;
++ }
++
++ /*
++ * If this is the first set of changes we are sending to a
++ * backend, obtain a lock on running. We will release the lock
++ * when the last init cfg request completes. This allows for
++ * initializing the backends in parallel.
++ */
++
++ if (!txn_init_readers++ && mgmt_ds_lock(ds_ctx, 0) != 0) {
++ _dbg("Failed to lock DS:%s for init of BE adapter '%s'",
++ mgmt_ds_id2name(MGMTD_DS_RUNNING), adapter->name);
++ --txn_init_readers;
++ return -1;
++ }
++
++ /* Get config for this single backend client */
++ adapter_cfgs = mgmt_be_adapter_get_config(adapter);
++ if (RB_EMPTY(nb_config_cbs, &adapter_cfgs)) {
++ if (!--txn_init_readers)
++ mgmt_ds_unlock(ds_ctx, 0);
++ return 0;
++ }
++
++ /*
++ * Create a CONFIG transaction to push the config changes
++ * provided to the backend client.
++ */
++ txn = txn_create(MGMTD_TXN_TYPE_CONFIG);
++ if (!txn) {
++ _log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'",
++ adapter->name);
++ if (!--txn_init_readers)
++ mgmt_ds_unlock(ds_ctx, 0);
++ nb_config_diff_del_changes(&adapter_cfgs);
++ return -1;
++ }
++
++ _dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", txn->txn_id,
++ adapter->name);
++
++ /*
++ * Set the changeset for transaction to commit and trigger the
++ * commit request.
++ */
++ memset(&adapter->cfg_stats, 0, sizeof(adapter->cfg_stats));
++ ccreq = txn_req_commit_alloc(txn, 0);
++ ccreq->src_ds_id = MGMTD_DS_NONE;
++ ccreq->src_ds_ctx = 0;
++ ccreq->dst_ds_id = MGMTD_DS_RUNNING;
++ ccreq->dst_ds_ctx = ds_ctx;
++ ccreq->validate_only = false;
++ ccreq->abort = false;
++ ccreq->init = true;
++ ccreq->cmt_stats = &adapter->cfg_stats;
++
++ /*
++ * Apply the initial changes.
++ */
++ return txn_cfg_send_config_changes(ccreq, &adapter_cfgs, IDBIT_MASK(adapter->id));
++}
++
++/* static */ void txn_cfg_txn_be_client_disconnect(struct mgmt_txn *txn,
++ struct mgmt_be_client_adapter *adapter)
++{
++ struct txn_req_commit *ccreq = txn_txn_req_commit(txn);
++
++ if (!ccreq)
++ return;
++
++ UNSET_IDBIT(ccreq->clients, adapter->id);
++ if (IS_IDBIT_SET(ccreq->clients_wait, adapter->id))
++ txn_cfg_adapter_acked(ccreq, adapter);
++}
++
++
++/* ================================================ */
++/* APIs for Frontend Clients (vtysh, restconf, ...) */
++/* ================================================ */
++
++/*
++ * Prepare and send config changes by comparing the source and destination
++ * datastores.
++ */
++static int txn_get_config_changes(struct txn_req_commit *ccreq, struct nb_config_cbs *cfg_chgs)
++{
++ struct nb_config *nb_config;
++ struct txn_req *txn_req = &ccreq->req;
++ int ret = 0;
++
++ if (ccreq->src_ds_id != MGMTD_DS_CANDIDATE) {
++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM,
++ "Source DS cannot be any other than CANDIDATE!");
++ }
++
++ if (ccreq->dst_ds_id != MGMTD_DS_RUNNING) {
++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM,
++ "Destination DS cannot be any other than RUNNING!");
++ }
++
++ if (!ccreq->src_ds_ctx) {
++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM,
++ "No such source datastore!");
++ }
++
++ if (!ccreq->dst_ds_ctx) {
++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM,
++ "No such destination datastore!");
++ }
++
++ if (ccreq->abort) {
++ /*
++ * This is a commit abort request. Return back success.
++ * The reply routing special cases abort, this isn't pretty,
++ * fix in later cleanup.
++ */
++ return txn_set_config_error(txn_req, MGMTD_SUCCESS, "commit abort");
++ }
++
++ nb_config = mgmt_ds_get_nb_config(ccreq->src_ds_ctx);
++ if (!nb_config) {
++ return txn_set_config_error(txn_req, MGMTD_INTERNAL_ERROR,
++ "Unable to retrieve Commit DS Config Tree!");
++ }
++
++ /*
++ * Validate YANG contents of the source DS and get the diff
++ * between source and destination DS contents.
++ */
++ char err_buf[BUFSIZ] = { 0 };
++
++ ret = nb_candidate_validate_yang(nb_config, true, err_buf, sizeof(err_buf) - 1);
++ if (ret != NB_OK) {
++ if (strncmp(err_buf, " ", strlen(err_buf)) == 0)
++ strlcpy(err_buf, "Validation failed", sizeof(err_buf));
++ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, err_buf);
++ }
++
++ nb_config_diff(mgmt_ds_get_nb_config(ccreq->dst_ds_ctx), nb_config, cfg_chgs);
++ if (RB_EMPTY(nb_config_cbs, cfg_chgs)) {
++ return txn_set_config_error(txn_req, MGMTD_NO_CFG_CHANGES,
++ "No changes found to be committed!");
++ }
++ return 0;
++}
++
++/**
++ * mgmt_txn_send_commit_config_req() - Send a commit config request
++ * @txn_id - A config TXN which must exist.
++ *
++ * Return: -1 on setup failure -- should immediately handle error, Otherwise 0
++ * and will reply to session with any downstream error.
++ */
++void mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id,
++ struct mgmt_ds_ctx *src_ds_ctx, enum mgmt_ds_id dst_ds_id,
++ struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort,
++ bool implicit, bool unlock, struct mgmt_edit_req *edit)
++{
++ struct mgmt_txn *txn;
++ struct txn_req_commit *ccreq;
++ struct nb_config_cbs changes = { 0 };
++ int ret;
++
++ txn = txn_lookup(txn_id);
++ assert(txn && txn->type == MGMTD_TXN_TYPE_CONFIG);
++
++ /* Only one outstanding commit request per txn */
++ assert(txn_txn_req_commit(txn) == NULL);
++
++ ccreq = txn_req_commit_alloc(txn, req_id);
++
++ /* Lock the datastores for this transaction */
++ assert(!mgmt_ds_txn_lock(src_ds_ctx, txn->txn_id));
++ assert(!mgmt_ds_txn_lock(dst_ds_ctx, txn->txn_id));
++ ccreq->txn_lock = true;
++
++ ccreq->src_ds_id = src_ds_id;
++ ccreq->src_ds_ctx = src_ds_ctx;
++ ccreq->dst_ds_id = dst_ds_id;
++ ccreq->dst_ds_ctx = dst_ds_ctx;
++ ccreq->validate_only = validate_only;
++ ccreq->abort = abort;
++ ccreq->implicit = implicit; /* this is only true iff edit */
++ ccreq->unlock_info = unlock; /* this is true for implicit commit in front-end */
++ ccreq->edit = edit;
++ ccreq->cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id);
++
++ ret = txn_get_config_changes(ccreq, &changes);
++ if (ret == 0)
++ ret = txn_cfg_send_config_changes(ccreq, &changes, 0);
++ if (ret)
++ txn_finish_commit(ccreq, ccreq->req.error, ccreq->req.err_info);
++}
++
++/*
++ * TODO: this needs to be fixed to be like a normal commit config request and
++ * use a VTY session not the special 0 session ID. We should only use 0 for the
++ * adapter init case.
++ */
++int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx,
++ struct mgmt_ds_ctx *dst_ds_ctx)
++{
++ static struct mgmt_commit_stats dummy_stats = { 0 };
++
++ struct nb_config_cbs changes = { 0 };
++ struct mgmt_txn *txn;
++ struct txn_req *txn_req;
++ struct txn_req_commit *ccreq;
++ int ret;
++
++ /*
++ * This could be the case when the config is directly
++ * loaded onto the candidate DS from a file. Get the
++ * diff from a full comparison of the candidate and
++ * running DSs.
++ */
++ nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx),
++ mgmt_ds_get_nb_config(src_ds_ctx), &changes);
++
++ if (RB_EMPTY(nb_config_cbs, &changes))
++ return -1;
++
++ /*
++ * Create a CONFIG transaction to push the config changes
++ * provided to the backend client.
++ */
++ txn = txn_create(MGMTD_TXN_TYPE_CONFIG);
++ if (!txn) {
++ _log_err("Failed to create CONFIG Transaction for downloading CONFIGs");
++ nb_config_diff_del_changes(&changes);
++ return -1;
++ }
++
++ _dbg("Created rollback txn-id: %" PRIu64, txn->txn_id);
++
++ /*
++ * Set the changeset for transaction to commit and trigger the commit
++ * request.
++ */
++ ccreq = txn_req_commit_alloc(txn, 0);
++ txn_req = as_txn_req(ccreq);
++ ccreq->src_ds_id = MGMTD_DS_CANDIDATE;
++ ccreq->src_ds_ctx = src_ds_ctx;
++ ccreq->dst_ds_id = MGMTD_DS_RUNNING;
++ ccreq->dst_ds_ctx = dst_ds_ctx;
++ ccreq->validate_only = false;
++ ccreq->abort = false;
++ ccreq->rollback = true;
++ ccreq->cmt_stats = &dummy_stats;
++
++ /*
++ * Send the changes.
++ */
++ ret = txn_cfg_send_config_changes(ccreq, &changes, 0);
++ if (ret)
++ txn_finish_commit(ccreq, txn_req->error, txn_req->err_info);
++ return ret;
++}
+diff --git a/mgmtd/mgmt_txn_priv.h b/mgmtd/mgmt_txn_priv.h
+new file mode 100644
+index 0000000000..25707773b9
+--- /dev/null
++++ b/mgmtd/mgmt_txn_priv.h
+@@ -0,0 +1,80 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * November 17 2025, Christian Hopps <chopps@labn.net>
++ *
++ * Copyright (c) 2025, LabN Consulting, L.L.C.
++ *
++ */
++#include <zebra.h>
++#include "mgmtd/mgmt.h"
++#include "mgmtd/mgmt_txn.h"
++#include "queue.h"
++
++#define _dbg(fmt, ...) DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__)
++#define _log_warn(fmt, ...) zlog_warn("%s: WARNING: " fmt, __func__, ##__VA_ARGS__)
++#define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__)
++
++#define TXN_INCREF(txn) txn_incref(txn, __FILE__, __LINE__)
++#define TXN_DECREF(txn) txn_decref(txn, __FILE__, __LINE__)
++
++/* ----------------- */
++/* Txn Private Types */
++/* ----------------- */
++
++enum txn_req_type {
++ TXN_REQ_TYPE_COMMIT = 1,
++ TXN_REQ_TYPE_GETTREE,
++ TXN_REQ_TYPE_RPC,
++};
++
++struct txn_req {
++ TAILQ_ENTRY(txn_req) link;
++ enum txn_req_type req_type;
++ uint64_t req_id;
++ struct mgmt_txn *txn;
++ int16_t error; /* cfg: MGMTD return code, else errno */
++ char *err_info; /* darr str */
++ struct event *timeout; /* for timing out the request */
++};
++#define as_txn_req(xreq) (&(xreq)->req) /* Macro to coerce specific req back into txn_req */
++
++struct mgmt_txn {
++ TAILQ_ENTRY(mgmt_txn) link;
++ enum mgmt_txn_type type;
++ uint64_t txn_id;
++ int refcount;
++ uint64_t session_id; /* One transaction per client session */
++ /* List of pending requests */
++ TAILQ_HEAD(txn_req_head, txn_req) reqs;
++};
++
++/* ---------------------------- */
++/* TXN Private Config Functions */
++/* ---------------------------- */
++
++extern void txn_cfg_cleanup(struct txn_req *txn_req);
++extern void txn_cfg_handle_error(struct txn_req *txn_req, struct mgmt_be_client_adapter *adapter,
++ int error, const char *errstr);
++extern int txn_cfg_be_client_connect(struct mgmt_be_client_adapter *adapter);
++extern void txn_cfg_txn_be_client_disconnect(struct mgmt_txn *txn,
++ struct mgmt_be_client_adapter *adapter);
++/* ---------------------------- */
++/* TXN Private Shared Functions */
++/* ---------------------------- */
++
++extern struct txn_req *txn_req_alloc(struct mgmt_txn *txn, uint64_t req_id,
++ enum txn_req_type req_type, size_t size);
++extern void txn_req_free(struct txn_req *txn_req);
++extern struct mgmt_txn *txn_create(enum mgmt_txn_type type);
++extern void mgmt_txn_cleanup_txn(struct mgmt_txn **txn);
++extern struct mgmt_txn *txn_lookup(uint64_t txn_id);
++extern void txn_decref(struct mgmt_txn *txn, const char *file, int line);
++
++extern struct event_loop *mgmt_txn_tm;
++
++/* ------------------- */
++/* TXN Private Globals */
++/* ------------------- */
++
++extern struct event_loop *mgmt_txn_tm;
++extern struct mgmt_master *mgmt_txn_mm;
+diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c
+index a8f7827f28..a9d2ce13ea 100644
+--- a/mgmtd/mgmt_vty.c
++++ b/mgmtd/mgmt_vty.c
+@@ -29,8 +29,6 @@
+ #include "staticd/static_vty.h"
+ #include "zebra/zebra_cli.h"
+
+-extern struct frr_daemon_info *mgmt_daemon_info;
+-
+ DEFPY(show_mgmt_be_adapter,
+ show_mgmt_be_adapter_cmd,
+ "show mgmt backend-adapter all",
+@@ -147,6 +145,11 @@ DEFPY(mgmt_commit,
+ bool validate_only = type[0] == 'c';
+ bool abort = type[1] == 'b';
+
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "Warning: candidate datastore is not locked.\n");
++ if (!validate_only && !vty->mgmt_locked_running_ds)
++ vty_out(vty, "Warning: running datastore is not locked.\n");
++
+ if (vty_mgmt_send_commit_config(vty, validate_only, abort, false) != 0)
+ return CMD_WARNING_CONFIG_FAILED;
+ return CMD_SUCCESS;
+@@ -159,12 +162,14 @@ DEFPY(mgmt_create_config_data, mgmt_create_config_data_cmd,
+ "XPath expression specifying the YANG data path\n"
+ "Value of the data to create\n")
+ {
+- strlcpy(vty->cfg_changes[0].xpath, path,
+- sizeof(vty->cfg_changes[0].xpath));
++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath));
+ vty->cfg_changes[0].value = value;
+ vty->cfg_changes[0].operation = NB_OP_CREATE_EXCL;
+ vty->num_cfg_changes = 1;
+
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "Warning: candidate datastore is not locked.\n");
++
+ return vty_mgmt_send_config_data(vty, NULL, false);
+ }
+
+@@ -175,12 +180,14 @@ DEFPY(mgmt_set_config_data, mgmt_set_config_data_cmd,
+ "XPath expression specifying the YANG data path\n"
+ "Value of the data to set\n")
+ {
+- strlcpy(vty->cfg_changes[0].xpath, path,
+- sizeof(vty->cfg_changes[0].xpath));
++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath));
+ vty->cfg_changes[0].value = value;
+ vty->cfg_changes[0].operation = NB_OP_MODIFY;
+ vty->num_cfg_changes = 1;
+
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "Warning: candidate datastore is not locked.\n");
++
+ return vty_mgmt_send_config_data(vty, NULL, false);
+ }
+
+@@ -190,13 +197,14 @@ DEFPY(mgmt_delete_config_data, mgmt_delete_config_data_cmd,
+ "Delete configuration data\n"
+ "XPath expression specifying the YANG data path\n")
+ {
+-
+- strlcpy(vty->cfg_changes[0].xpath, path,
+- sizeof(vty->cfg_changes[0].xpath));
++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath));
+ vty->cfg_changes[0].value = NULL;
+ vty->cfg_changes[0].operation = NB_OP_DELETE;
+ vty->num_cfg_changes = 1;
+
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "Warning: candidate datastore is not locked.\n");
++
+ return vty_mgmt_send_config_data(vty, NULL, false);
+ }
+
+@@ -206,13 +214,14 @@ DEFPY(mgmt_remove_config_data, mgmt_remove_config_data_cmd,
+ "Remove configuration data\n"
+ "XPath expression specifying the YANG data path\n")
+ {
+-
+- strlcpy(vty->cfg_changes[0].xpath, path,
+- sizeof(vty->cfg_changes[0].xpath));
++ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath));
+ vty->cfg_changes[0].value = NULL;
+ vty->cfg_changes[0].operation = NB_OP_DESTROY;
+ vty->num_cfg_changes = 1;
+
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "Warning: candidate datastore is not locked.\n");
++
+ return vty_mgmt_send_config_data(vty, NULL, false);
+ }
+
+@@ -230,6 +239,9 @@ DEFPY(mgmt_replace_config_data, mgmt_replace_config_data_cmd,
+ vty->cfg_changes[0].operation = NB_OP_REPLACE;
+ vty->num_cfg_changes = 1;
+
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "Warning: candidate datastore is not locked.\n");
++
+ return vty_mgmt_send_config_data(vty, NULL, false);
+ }
+
+@@ -251,7 +263,7 @@ DEFPY(mgmt_edit, mgmt_edit_cmd,
+ {
+ LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON;
+ uint8_t operation;
+- uint8_t flags = 0;
++ uint8_t ds_id;
+
+ switch (op[2]) {
+ case 'e':
+@@ -280,14 +292,8 @@ DEFPY(mgmt_edit, mgmt_edit_cmd,
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+- if (lock)
+- flags |= EDIT_FLAG_IMPLICIT_LOCK;
+-
+- if (commit)
+- flags |= EDIT_FLAG_IMPLICIT_COMMIT;
+-
+- return vty_mgmt_send_edit_req(vty, MGMT_MSG_DATASTORE_CANDIDATE, format, flags, operation,
+- xpath, data);
++ ds_id = commit ? MGMT_MSG_DATASTORE_RUNNING : MGMT_MSG_DATASTORE_CANDIDATE;
++ return vty_mgmt_send_edit_req(vty, ds_id, format, 0, operation, xpath, data);
+ }
+
+ DEFPY(mgmt_rpc, mgmt_rpc_cmd,
+@@ -426,7 +432,7 @@ DEFPY(show_mgmt_map_xpath,
+ "Get YANG Backend Subscription\n"
+ "XPath expression specifying the YANG data path\n")
+ {
+- mgmt_be_show_xpath_registries(vty, path);
++ mgmt_be_adapter_show_xpath_registries(vty, path);
+ return CMD_SUCCESS;
+ }
+
+@@ -568,18 +574,6 @@ DEFPY(debug_mgmt, debug_mgmt_cmd,
+ return CMD_SUCCESS;
+ }
+
+-static void mgmt_config_read_in(struct event *event)
+-{
+- if (vty_mgmt_fe_enabled())
+- mgmt_vty_read_configs();
+- else {
+- zlog_warn("%s: no connection to front-end server, retry in 1s",
+- __func__);
+- event_add_timer(mm->master, mgmt_config_read_in, NULL, 1,
+- &mgmt_daemon_info->read_in);
+- }
+-}
+-
+ static int mgmtd_config_write(struct vty *vty)
+ {
+ struct lyd_node *root;
+@@ -624,10 +618,6 @@ void mgmt_vty_init(void)
+ #ifdef HAVE_STATICD
+ static_vty_init();
+ #endif
+-
+- event_add_event(mm->master, mgmt_config_read_in, NULL, 0,
+- &mgmt_daemon_info->read_in);
+-
+ install_node(&mgmtd_node);
+
+ install_element(VIEW_NODE, &show_mgmt_be_adapter_cmd);
+diff --git a/mgmtd/mgmt_vty_frontend.c b/mgmtd/mgmt_vty_frontend.c
+new file mode 100644
+index 0000000000..a4f7cc0f7d
+--- /dev/null
++++ b/mgmtd/mgmt_vty_frontend.c
+@@ -0,0 +1,898 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * November 15 2025, Christian Hopps <chopps@labn.net>
++ *
++ * Copyright (c) 2025, LabN Consulting, L.L.C.
++ *
++ */
++
++#include <zebra.h>
++#include <libyang/libyang.h>
++#include <libyang/version.h>
++#include "lib/debug.h"
++#include "lib/command.h"
++#include "lib/lib_vty.h"
++#include "lib/northbound_cli.h"
++#include "lib/vty.h"
++#include "mgmtd/mgmt.h"
++
++#define _dbg debug_fe_client
++#define _log_err log_err_fe_client
++#define _log_warn(fmt, ...) zlog_warn("FE-CLIENT: %s: WARNING: " fmt, __func__, ##__VA_ARGS__)
++
++/* static */ struct mgmt_fe_client *mgmt_fe_client;
++static bool mgmt_fe_connected;
++
++static uint64_t mgmt_client_id_next;
++
++char const *const mgmt_daemons[] = {
++ "zebra",
++#ifdef HAVE_RIPD
++ "ripd",
++#endif
++#ifdef HAVE_RIPNGD
++ "ripngd",
++#endif
++#ifdef HAVE_STATICD
++ "staticd",
++#endif
++};
++uint mgmt_daemons_count = array_size(mgmt_daemons);
++
++
++/* ================= */
++/* Utility Functions */
++/* ================= */
++
++static bool vty_mgmt_fe_enabled(void)
++{
++ return mgmt_fe_client && mgmt_fe_connected;
++}
++
++static void fe_client_set_vty_callbacks(bool connected);
++static int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok);
++
++static int vty_mgmt_lock_candidate_inline(struct vty *vty)
++{
++ assert(!vty->mgmt_locked_candidate_ds);
++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true);
++ return vty->mgmt_locked_candidate_ds ? 0 : -1;
++}
++
++static int vty_mgmt_unlock_candidate_inline(struct vty *vty)
++{
++ assert(vty->mgmt_locked_candidate_ds);
++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true);
++ return vty->mgmt_locked_candidate_ds ? -1 : 0;
++}
++
++static int vty_mgmt_lock_running_inline(struct vty *vty)
++{
++ assert(!vty->mgmt_locked_running_ds);
++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true);
++ return vty->mgmt_locked_running_ds ? 0 : -1;
++}
++
++static int vty_mgmt_unlock_running_inline(struct vty *vty)
++{
++ assert(vty->mgmt_locked_running_ds);
++ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true);
++ return vty->mgmt_locked_running_ds ? -1 : 0;
++}
++
++void vty_mgmt_resume_response(struct vty *vty, int ret)
++{
++ if (!vty->mgmt_req_pending_cmd) {
++ zlog_err("vty resume response called without mgmt_req_pending_cmd");
++ return;
++ }
++
++ debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64 " with '%s'",
++ vty->mgmt_req_pending_cmd, vty->mgmt_session_id,
++ ret == CMD_SUCCESS ? "success" : "failed");
++
++ vty->mgmt_req_pending_cmd = NULL;
++
++ vty_resume_response(vty, ret);
++}
++
++/* ======================================================= */
++/* Startup Read Config Files for all mgmt-enabled daemons. */
++/* ======================================================= */
++
++static bool mgmt_vty_read_configs(void)
++{
++ char path[PATH_MAX];
++ struct vty *vty;
++ FILE *confp;
++ uint line_num = 0;
++ uint count = 0;
++ uint index;
++
++ vty = vty_new();
++ vty->wfd = STDERR_FILENO;
++ vty->type = VTY_FILE; /* We don't send these changes to backends */
++ vty->node = CONFIG_NODE;
++ vty->config = true;
++ vty->pending_allowed = true;
++
++ vty->candidate_config = vty_shared_candidate_config;
++
++ vty_mgmt_lock_candidate_inline(vty);
++ vty_mgmt_lock_running_inline(vty);
++
++ for (index = 0; index < array_size(mgmt_daemons); index++) {
++ snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, mgmt_daemons[index]);
++
++ confp = vty_open_config(path, config_default);
++ if (!confp)
++ continue;
++
++ zlog_info("mgmtd: reading config file: %s", path);
++
++ /* Execute configuration file */
++ line_num = 0;
++ (void)config_from_file(vty, confp, &line_num);
++ count++;
++
++ fclose(confp);
++ }
++
++ snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir);
++ confp = vty_open_config(path, config_default);
++ if (confp) {
++ zlog_info("mgmtd: reading config file: %s", path);
++
++ line_num = 0;
++ (void)config_from_file(vty, confp, &line_num);
++ count++;
++
++ fclose(confp);
++ }
++
++ /* Conditionally unlock as the config file may have "exit"d early which
++ * would then have unlocked things.
++ */
++ if (vty->mgmt_locked_running_ds)
++ vty_mgmt_unlock_running_inline(vty);
++ if (vty->mgmt_locked_candidate_ds)
++ vty_mgmt_unlock_candidate_inline(vty);
++
++ vty->pending_allowed = false;
++
++ if (!count)
++ vty_close(vty);
++ else
++ vty_read_file_finish(vty, NULL);
++
++ zlog_info("mgmtd: finished reading config files");
++
++ return true;
++}
++
++
++/*
++ * This is analogous to frr_config_read_in() in libfrr.c, but customized for
++ * mgmtd. It reads in all the mgmt-enabled daemon config files, this method is
++ * now deprecrated in favor of integrated config via vtysh.
++ */
++static void mgmt_config_read_in(struct event *event)
++{
++ if (vty_mgmt_fe_enabled())
++ mgmt_vty_read_configs();
++ else {
++ zlog_warn("%s: no connection to front-end server, retry in 1s", __func__);
++ event_add_timer(mm->master, mgmt_config_read_in, NULL, 1,
++ &mgmt_daemon_info->read_in);
++ }
++}
++
++static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf, size_t count)
++{
++ struct vty *vty = user_data;
++
++ vty_out(vty, "%.*s", (int)count, (const char *)buf);
++ return count;
++}
++
++static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format, const struct ly_err_item *ei)
++{
++#if (LY_VERSION_MAJOR < 3)
++#define data_path path
++#else
++#define data_path data_path
++#endif
++ bool have_apptag = ei->apptag && ei->apptag[0] != 0;
++ bool have_path = ei->data_path && ei->data_path[0] != 0;
++ bool have_msg = ei->msg && ei->msg[0] != 0;
++ const char *severity = NULL;
++ const char *evalid = NULL;
++ const char *ecode = NULL;
++#if (LY_VERSION_MAJOR < 3)
++ LY_ERR err = ei->no;
++#else
++ LY_ERR err = ei->err;
++#endif
++
++ if (ei->level == LY_LLERR)
++ severity = "error";
++ else if (ei->level == LY_LLWRN)
++ severity = "warning";
++
++ ecode = yang_ly_strerrcode(err);
++ if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS)
++ evalid = yang_ly_strvecode(ei->vecode);
++
++ switch (format) {
++ case LYD_XML:
++ vty_out(vty, "<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
++ vty_out(vty, "<error-type>application</error-type>");
++ if (severity)
++ vty_out(vty, "<error-severity>%s</error-severity>", severity);
++ if (ecode)
++ vty_out(vty, "<error-code>%s</error-code>", ecode);
++ if (evalid)
++ vty_out(vty, "<error-validation>%s</error-validation>\n", evalid);
++ if (have_path)
++ vty_out(vty, "<error-path>%s</error-path>\n", ei->data_path);
++ if (have_apptag)
++ vty_out(vty, "<error-app-tag>%s</error-app-tag>\n", ei->apptag);
++ if (have_msg)
++ vty_out(vty, "<error-message>%s</error-message>\n", ei->msg);
++
++ vty_out(vty, "</rpc-error>");
++ break;
++ case LYD_JSON:
++ vty_out(vty, "{ \"error-type\": \"application\"");
++ if (severity)
++ vty_out(vty, ", \"error-severity\": \"%s\"", severity);
++ if (ecode)
++ vty_out(vty, ", \"error-code\": \"%s\"", ecode);
++ if (evalid)
++ vty_out(vty, ", \"error-validation\": \"%s\"", evalid);
++ if (have_path)
++ vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path);
++ if (have_apptag)
++ vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag);
++ if (have_msg)
++ vty_out(vty, ", \"error-message\": \"%s\"", ei->msg);
++
++ vty_out(vty, "}");
++ break;
++ case LYD_UNKNOWN:
++ case LYD_LYB:
++ default:
++ vty_out(vty, "%% error");
++ if (severity)
++ vty_out(vty, " severity: %s", severity);
++ if (evalid)
++ vty_out(vty, " invalid: %s", evalid);
++ if (have_path)
++ vty_out(vty, " path: %s", ei->data_path);
++ if (have_apptag)
++ vty_out(vty, " app-tag: %s", ei->apptag);
++ if (have_msg)
++ vty_out(vty, " msg: %s", ei->msg);
++ break;
++ }
++#undef data_path
++}
++
++static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format)
++{
++ const struct ly_err_item *ei = ly_err_first(ly_native_ctx);
++ uint count;
++
++ if (!ei)
++ return 0;
++
++ if (format == LYD_JSON)
++ vty_out(vty, "\"ietf-restconf:errors\": [ ");
++
++ for (count = 0; ei; count++, ei = ei->next) {
++ if (count)
++ vty_out(vty, ", ");
++ vty_out_yang_error(vty, format, ei);
++ }
++
++ if (format == LYD_JSON)
++ vty_out(vty, " ]");
++
++ ly_err_clean(ly_native_ctx, NULL);
++
++ return count;
++}
++
++
++static int vty_mgmt_handle_error_reply(struct mgmt_fe_client *client, uintptr_t user_data,
++ uint64_t client_id, uint64_t session_id, uintptr_t session_ctx,
++ uint64_t req_id, int error, const char *errstr)
++{
++ struct vty *vty = (struct vty *)session_ctx;
++ const char *cname = mgmt_fe_client_name(client);
++
++ if (!vty->mgmt_req_pending_cmd) {
++ debug_fe_client("Error with no pending command: %d returned for client %s 0x%Lx session-id %Lu req-id %Lu error-str %s",
++ error, cname, client_id, session_id, req_id, errstr);
++ vty_out(vty, "%% Error %d from MGMTD for %s with no pending command: %s\n", error,
++ cname, errstr);
++ return CMD_WARNING;
++ }
++
++ debug_fe_client("Error %d returned for client %s 0x%" PRIx64 " session-id %" PRIu64
++ " req-id %" PRIu64 "error-str %s",
++ error, cname, client_id, session_id, req_id, errstr);
++
++ vty_out(vty, "%% %s (for %s, client %s)\n", errstr, vty->mgmt_req_pending_cmd, cname);
++
++ vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS);
++
++ return 0;
++}
++
++/* =================================== */
++/* Mgmtd Frontend Client Functionality */
++/* =================================== */
++
++/* ------- */
++/* Locking */
++/* ------- */
++
++static int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok)
++{
++ assert(mgmt_fe_client);
++ assert(vty->mgmt_session_id);
++
++ vty->mgmt_req_id++;
++ if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, ds_id,
++ lock, scok)) {
++ zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, lock ? "" : "UN",
++ vty->mgmt_req_id);
++ vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", lock ? "" : "UN");
++ return -1;
++ }
++
++ if (!scok)
++ vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ";
++
++ return 0;
++}
++
++static void vty_mgmt_handle_lock_ds_reply(struct mgmt_fe_client *client, uintptr_t usr_data,
++ uint64_t client_id, uintptr_t session_id,
++ uintptr_t session_ctx, uint64_t req_id, bool lock_ds,
++ bool success, enum mgmt_ds_id ds_id, char *errmsg_if_any)
++{
++ struct vty *vty;
++ bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client);
++
++ vty = (struct vty *)session_ctx;
++
++ assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING);
++ if (!success)
++ zlog_err("%socking for DS %u failed, Err: '%s' vty %p", lock_ds ? "L" : "Unl",
++ ds_id, errmsg_if_any, vty);
++ else {
++ debug_fe_client("%socked DS %u successfully", lock_ds ? "L" : "Unl", ds_id);
++ if (ds_id == MGMTD_DS_CANDIDATE)
++ vty->mgmt_locked_candidate_ds = lock_ds;
++ else
++ vty->mgmt_locked_running_ds = lock_ds;
++ }
++
++ if (!is_short_circuit && vty->mgmt_req_pending_cmd) {
++ assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ"));
++ vty_mgmt_resume_response(vty, success ? CMD_SUCCESS : CMD_WARNING);
++ }
++}
++
++/* ------------------------------------------------ */
++/* "Send" Config Data -- actually just edits inline */
++/* ------------------------------------------------ */
++
++int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, bool implicit_commit)
++{
++ char err_buf[BUFSIZ];
++ bool error = false;
++
++ if (implicit_commit) {
++ assert(vty->mgmt_client_id && vty->mgmt_session_id);
++ if (vty_mgmt_lock_candidate_inline(vty)) {
++ vty_out(vty, "%% could not lock candidate DS\n");
++ return CMD_WARNING_CONFIG_FAILED;
++ } else if (vty_mgmt_lock_running_inline(vty)) {
++ vty_out(vty, "%% could not lock running DS\n");
++ vty_mgmt_unlock_candidate_inline(vty);
++ return CMD_WARNING_CONFIG_FAILED;
++ }
++ }
++
++ if (!vty->mgmt_locked_candidate_ds)
++ vty_out(vty, "%% WARNING: changing candidate datastore without lock.\n");
++
++ nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes,
++ vty->num_cfg_changes, xpath_base, false, err_buf,
++ sizeof(err_buf), &error);
++ if (error) {
++ /*
++ * Failure to edit the candidate configuration should never
++ * happen in practice, unless there's a bug in the code. When
++ * that happens, log the error but otherwise ignore it.
++ */
++ vty_out(vty, "%% Couldn't apply changes: %s", err_buf);
++error:
++ if (implicit_commit) {
++ vty_mgmt_unlock_running_inline(vty);
++ vty_mgmt_unlock_candidate_inline(vty);
++ }
++ return CMD_WARNING_CONFIG_FAILED;
++ }
++
++ if (!implicit_commit)
++ return CMD_SUCCESS;
++
++ assert(vty->mgmt_client_id && vty->mgmt_session_id);
++ if (vty_mgmt_send_commit_config(vty, false, false, true) < 0)
++ goto error;
++
++ return CMD_SUCCESS;
++}
++
++/* ------------- */
++/* Commit Config */
++/* ------------- */
++
++int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock)
++{
++ if (mgmt_fe_client && vty->mgmt_session_id) {
++ vty->mgmt_req_id++;
++ if (mgmt_fe_send_commit_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id,
++ MGMTD_DS_CANDIDATE, MGMTD_DS_RUNNING, validate_only,
++ abort, unlock)) {
++ zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, vty->mgmt_req_id);
++ vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n");
++ return -1;
++ }
++
++ vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ";
++ vty->mgmt_num_pending_setcfg = 0;
++ }
++
++ return 0;
++}
++
++static void vty_mgmt_handle_commit_config_reply(struct mgmt_fe_client *client, uintptr_t usr_data,
++ uint64_t client_id, uintptr_t session_id,
++ uintptr_t session_ctx, uint64_t req_id,
++ bool success, enum mgmt_ds_id src_ds_id,
++ enum mgmt_ds_id dst_ds_id, bool validate_only,
++ bool unlock, char *errmsg_if_any)
++{
++ struct vty *vty;
++
++ vty = (struct vty *)session_ctx;
++
++ if (!success) {
++ zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 " failed, Error: '%s'",
++ client_id, errmsg_if_any ? errmsg_if_any : "Unknown");
++ vty_out(vty, "%% Configuration failed.\n\n");
++ if (errmsg_if_any)
++ vty_out(vty, "%s\n", errmsg_if_any);
++ } else {
++ debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 " req-id %" PRIu64
++ " was successfull%s%s",
++ client_id, req_id, errmsg_if_any ? ": " : "", errmsg_if_any ?: "");
++ if (!unlock && errmsg_if_any)
++ vty_out(vty, "MGMTD: %s\n", errmsg_if_any);
++ }
++
++ if (unlock) {
++ /* we locked these when we sent the commit, unlock now */
++ vty_mgmt_unlock_candidate_inline(vty);
++ vty_mgmt_unlock_running_inline(vty);
++ }
++
++ vty_mgmt_resume_response(vty, success ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED);
++}
++
++/* -------- */
++/* Get Data */
++/* -------- */
++
++int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type,
++ uint8_t flags, uint8_t defaults, const char *xpath)
++{
++ LYD_FORMAT intern_format = result_type;
++
++ vty->mgmt_req_id++;
++
++ if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id,
++ datastore, intern_format, flags, defaults, xpath)) {
++ zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 " req-id %" PRIu64
++ ".",
++ vty->mgmt_session_id, vty->mgmt_req_id);
++ vty_out(vty, "Failed to send GET-DATA to MGMTD!\n");
++ return -1;
++ }
++
++ vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ";
++ vty->mgmt_req_pending_data = result_type;
++
++ return 0;
++}
++
++static int vty_mgmt_handle_get_tree_reply(struct mgmt_fe_client *client, uintptr_t user_data,
++ uint64_t client_id, uint64_t session_id,
++ uintptr_t session_ctx, uint64_t req_id,
++ enum mgmt_ds_id ds_id, LYD_FORMAT result_type,
++ void *result, size_t len, int partial_error)
++{
++ struct vty *vty;
++ struct lyd_node *dnode;
++ int ret = CMD_SUCCESS;
++ LY_ERR err;
++
++ vty = (struct vty *)session_ctx;
++
++ debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64 " req-id %" PRIu64,
++ partial_error ? "partially " : "", client_id, req_id);
++
++ assert(result_type == LYD_LYB || result_type == vty->mgmt_req_pending_data);
++
++ if (vty->mgmt_req_pending_data == LYD_XML && partial_error)
++ vty_out(vty, "<!-- some errors occurred gathering results -->\n");
++
++ if (result_type == LYD_LYB) {
++ /*
++ * parse binary into tree and print in the specified format
++ */
++ result_type = vty->mgmt_req_pending_data;
++
++ err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0, &dnode);
++ if (!err)
++ err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode, result_type,
++ LYD_PRINT_WITHSIBLINGS);
++ lyd_free_all(dnode);
++
++ if (vty_out_yang_errors(vty, result_type) || err)
++ ret = CMD_WARNING;
++ } else {
++ /*
++ * Print the in-format result
++ */
++ assert(result_type == LYD_XML || result_type == LYD_JSON);
++ vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result);
++ }
++
++ vty_mgmt_resume_response(vty, ret);
++
++ return 0;
++}
++
++
++/* ----------- */
++/* Edit Config */
++/* ----------- */
++
++int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type,
++ uint8_t flags, uint8_t operation, const char *xpath, const char *data)
++{
++ vty->mgmt_req_id++;
++
++ if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id,
++ datastore, request_type, flags, operation, xpath, data)) {
++ zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".",
++ vty->mgmt_session_id, vty->mgmt_req_id);
++ vty_out(vty, "Failed to send EDIT to MGMTD!\n");
++ return -1;
++ }
++
++ vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ";
++
++ return 0;
++}
++
++static int vty_mgmt_handle_edit_reply(struct mgmt_fe_client *client, uintptr_t user_data,
++ uint64_t client_id, uint64_t session_id,
++ uintptr_t session_ctx, uint64_t req_id, const char *xpath)
++{
++ struct vty *vty = (struct vty *)session_ctx;
++
++ debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64
++ " was successful, xpath: %s",
++ client_id, req_id, xpath);
++
++ vty_mgmt_resume_response(vty, CMD_SUCCESS);
++
++ return 0;
++}
++
++
++/* =========== */
++/* Execute RPC */
++/* =========== */
++
++int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, const char *xpath,
++ const char *data)
++{
++ vty->mgmt_req_id++;
++
++ if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id,
++ request_type, xpath, data)) {
++ zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".",
++ vty->mgmt_session_id, vty->mgmt_req_id);
++ vty_out(vty, "Failed to send RPC to MGMTD!\n");
++ return -1;
++ }
++
++ vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ";
++
++ return 0;
++}
++
++static int vty_mgmt_handle_rpc_reply(struct mgmt_fe_client *client, uintptr_t user_data,
++ uint64_t client_id, uint64_t session_id,
++ uintptr_t session_ctx, uint64_t req_id, const char *result)
++{
++ struct vty *vty = (struct vty *)session_ctx;
++
++ debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 " was successful",
++ client_id, req_id);
++
++ if (result)
++ vty_out(vty, "%s\n", result);
++
++ vty_mgmt_resume_response(vty, CMD_SUCCESS);
++
++ return 0;
++}
++
++
++/* ==================================== */
++/* VTY Augmenting/Hooking Functionality */
++/* ==================================== */
++
++static void vty_new_mgmt(struct vty *new)
++{
++ if (!mgmt_fe_client)
++ return;
++ if (!mgmt_client_id_next)
++ mgmt_client_id_next++;
++ new->mgmt_client_id = mgmt_client_id_next++;
++ new->mgmt_session_id = 0;
++ mgmt_fe_create_client_session(mgmt_fe_client, new->mgmt_client_id, (uintptr_t)new);
++
++ /* we short-circuit create the session so it must be set now */
++ assertf(new->mgmt_session_id != 0, "Failed to create client session for VTY");
++}
++
++static void vty_close_mgmt(struct vty *vty)
++{
++ if (!mgmt_fe_client || !vty->mgmt_client_id)
++ return;
++
++ debug_fe_client("closing vty session");
++ mgmt_fe_destroy_client_session(mgmt_fe_client, vty->mgmt_client_id);
++ vty->mgmt_session_id = 0;
++}
++
++static int vty_config_enter_mgmt(struct vty *vty, bool private_config, bool exclusive,
++ bool file_lock)
++{
++ /* if no file lock requested, nothing to do */
++ if (!file_lock)
++ return CMD_SUCCESS;
++
++ /* Working on a private config is outside normal mgmtd actions */
++ if (private_config)
++ return CMD_SUCCESS;
++
++ /* Exclude is actually what we are doing with file-lock so it's no-op */
++
++ /*
++ * We only need to do a lock when reading a config file as we will be
++ * sending a batch of setcfg changes followed by a single commit
++ * message. For user interactive mode we are doing implicit commits
++ * those will obtain the lock (or not) when they try and commit.
++ */
++ if (vty_mgmt_lock_candidate_inline(vty)) {
++ vty_out(vty,
++ "%% Can't enter config; candidate datastore locked by another session\n");
++ return CMD_WARNING_CONFIG_FAILED;
++ }
++ if (vty_mgmt_lock_running_inline(vty)) {
++ vty_out(vty,
++ "%% Can't enter config; running datastore locked by another session\n");
++ vty_mgmt_unlock_candidate_inline(vty);
++ return CMD_WARNING_CONFIG_FAILED;
++ }
++ assert(vty->mgmt_locked_candidate_ds);
++ assert(vty->mgmt_locked_running_ds);
++
++ /*
++ * As datastores are locked explicitly, we don't need implicit commits
++ * and should allow pending changes.
++ */
++ vty->pending_allowed = true;
++
++ return CMD_SUCCESS;
++}
++
++static void vty_config_node_exit_mgmt(struct vty *vty)
++{
++ if (vty->mgmt_locked_running_ds)
++ vty_mgmt_unlock_running_inline(vty);
++
++ if (vty->mgmt_locked_candidate_ds)
++ vty_mgmt_unlock_candidate_inline(vty);
++}
++
++static void vty_end_config_mgmt(struct vty *vty)
++{
++ /*
++ * If we have made changes with vty_mgmt_send_config_data(), but without
++ * implicit commit then we need to do a commit now to apply all those
++ * pending changes.
++ */
++ if (vty->mgmt_num_pending_setcfg)
++ vty_mgmt_send_commit_config(vty, false, false, false);
++}
++
++static int nb_cli_apply_changes_mgmt(struct vty *vty, const char *xpath_base_abs)
++{
++ bool implicit_commit;
++
++ VTY_CHECK_XPATH;
++
++ assert(vty->type != VTY_FILE && vty_mgmt_fe_enabled());
++ /*
++ * The legacy user wanted to clear pending (i.e., perform a
++ * commit immediately) due to some non-yang compatible
++ * functionality. This new mgmtd code however, continues to send
++ * changes putting off the commit until XFRR_end is received
++ * (i.e., end-of-config-file). This should be fine b/c all
++ * conversions to mgmtd require full proper implementations.
++ */
++ if (!vty->num_cfg_changes)
++ return CMD_SUCCESS;
++
++ implicit_commit = frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed;
++ if (vty_mgmt_send_config_data(vty, xpath_base_abs, implicit_commit) < 0) {
++ vty_out(vty, "%% Failed to apply configuration data.\n");
++ return CMD_WARNING_CONFIG_FAILED;
++ }
++ if (!implicit_commit)
++ ++vty->mgmt_num_pending_setcfg;
++ return CMD_SUCCESS;
++}
++
++static int nb_cli_rpc_mgmt(struct vty *vty, const char *xpath, const struct lyd_node *input)
++{
++ char *data = NULL;
++ LY_ERR err;
++ int ret;
++
++ err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK);
++ assert(err == LY_SUCCESS);
++
++ ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data);
++
++ free(data);
++ if (ret < 0)
++ return CMD_WARNING;
++ return CMD_SUCCESS;
++}
++
++static void fe_client_set_vty_callbacks(bool connected)
++{
++ if (connected) {
++ /* only call when connected */
++ vty_config_enter_mgmt_cb = vty_config_enter_mgmt;
++ vty_config_node_exit_mgmt_cb = vty_config_node_exit_mgmt;
++ cmd_init_config_callbacks(NULL, vty_end_config_mgmt);
++ nb_cli_apply_changes_mgmt_cb = nb_cli_apply_changes_mgmt;
++ nb_cli_rpc_mgmt_cb = nb_cli_rpc_mgmt;
++ } else {
++ vty_config_enter_mgmt_cb = NULL;
++ vty_config_node_exit_mgmt_cb = NULL;
++ cmd_init_config_callbacks(NULL, NULL);
++ nb_cli_apply_changes_mgmt_cb = NULL;
++ nb_cli_rpc_mgmt_cb = NULL;
++ }
++}
++
++/* ====================== */
++/* Initialize and Cleanup */
++/* ====================== */
++
++static void vty_mgmt_client_connect_notified(struct mgmt_fe_client *client, uintptr_t usr_data,
++ bool connected)
++{
++ debug_fe_client("Got %sconnected %s MGMTD Frontend Server", !connected ? "dis: " : "",
++ !connected ? "from" : "to");
++
++ /*
++ * We should not have any sessions for connecting or disconnecting case.
++ * The fe client library will delete all session on disconnect before
++ * calling us.
++ */
++ assert(mgmt_fe_client_session_count(client) == 0);
++
++ mgmt_fe_connected = connected;
++ fe_client_set_vty_callbacks(connected);
++
++ /* Start or stop listening for vty connections */
++ if (connected)
++ frr_vty_serv_start(true);
++ else
++ frr_vty_serv_stop();
++}
++
++/*
++ * A session has successfully been created for a vty.
++ */
++static void vty_mgmt_client_session_notified(struct mgmt_fe_client *client, uintptr_t usr_data,
++ uint64_t client_id, bool create, bool success,
++ uintptr_t session_id, uintptr_t session_ctx)
++{
++ struct vty *vty;
++
++ vty = (struct vty *)session_ctx;
++
++ if (!success) {
++ zlog_err("%s session for client %" PRIu64 " failed!",
++ create ? "Creating" : "Destroying", client_id);
++ return;
++ }
++
++ debug_fe_client("%s session for client %" PRIu64 " successfully",
++ create ? "Created" : "Destroyed", client_id);
++
++ if (create) {
++ assert(session_id != 0);
++ vty->mgmt_session_id = session_id;
++ } else {
++ vty->mgmt_session_id = 0;
++ /* We may come here by way of vty_close() and short-circuits */
++ if (vty->status != VTY_CLOSE)
++ vty_close(vty);
++ }
++}
++
++
++static struct mgmt_fe_client_cbs mgmt_cbs = {
++ .client_connect_notify = vty_mgmt_client_connect_notified,
++ .client_session_notify = vty_mgmt_client_session_notified,
++ .lock_ds_notify = vty_mgmt_handle_lock_ds_reply,
++ .commit_config_notify = vty_mgmt_handle_commit_config_reply,
++ .get_tree_notify = vty_mgmt_handle_get_tree_reply,
++ .edit_notify = vty_mgmt_handle_edit_reply,
++ .rpc_notify = vty_mgmt_handle_rpc_reply,
++ .error_notify = vty_mgmt_handle_error_reply,
++
++};
++
++void vty_mgmt_init(void)
++{
++ char name[40];
++
++ assert(mm->master);
++ assert(!mgmt_fe_client);
++ snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), (long)getpid());
++ mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, mm->master);
++ vty_new_mgmt_cb = vty_new_mgmt;
++ vty_close_mgmt_cb = vty_close_mgmt;
++ assert(mgmt_fe_client);
++
++ event_add_event(mm->master, mgmt_config_read_in, NULL, 0, &mgmt_daemon_info->read_in);
++}
++
++void vty_mgmt_terminate(void)
++{
++ if (mgmt_fe_client) {
++ mgmt_fe_client_destroy(mgmt_fe_client);
++ mgmt_fe_client = NULL;
++ vty_new_mgmt_cb = NULL;
++ vty_close_mgmt_cb = NULL;
++ }
++}
+diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am
+index 14544c4f05..bbe272d84e 100644
+--- a/mgmtd/subdir.am
++++ b/mgmtd/subdir.am
+@@ -34,7 +34,9 @@ mgmtd_libmgmtd_a_SOURCES = \
+ mgmtd/mgmt_history.c \
+ mgmtd/mgmt_memory.c \
+ mgmtd/mgmt_txn.c \
++ mgmtd/mgmt_txn_cfg.c \
+ mgmtd/mgmt_vty.c \
++ mgmtd/mgmt_vty_frontend.c \
+ # end
+
+ noinst_HEADERS += \
+@@ -45,6 +47,7 @@ noinst_HEADERS += \
+ mgmtd/mgmt_history.h \
+ mgmtd/mgmt_memory.h \
+ mgmtd/mgmt_txn.h \
++ mgmtd/mgmt_txn_priv.h \
+ zebra/zebra_cli.h \
+ # end
+
+diff --git a/ospf6d/ospf6_main.c b/ospf6d/ospf6_main.c
+index 4aa5771a79..5d7c7ee245 100644
+--- a/ospf6d/ospf6_main.c
++++ b/ospf6d/ospf6_main.c
+@@ -210,7 +210,7 @@ static void ospf6_config_finish(struct event *t)
+ OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS);
+ }
+
+-static void ospf6_config_start(void)
++static void ospf6_config_start(struct vty *vty)
+ {
+ if (IS_OSPF6_DEBUG_EVENT)
+ zlog_debug("ospf6d config start received");
+@@ -219,7 +219,7 @@ static void ospf6_config_start(void)
+ OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf6_cfg);
+ }
+
+-static void ospf6_config_end(void)
++static void ospf6_config_end(struct vty *vty)
+ {
+ if (IS_OSPF6_DEBUG_EVENT)
+ zlog_debug("ospf6d config end received");
+diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c
+index 17c6f3f196..7107af9637 100644
+--- a/ospfd/ospf_main.c
++++ b/ospfd/ospf_main.c
+@@ -180,7 +180,7 @@ static void ospf_config_finish(struct event *t)
+ OSPF_PRE_CONFIG_MAX_WAIT_SECONDS);
+ }
+
+-static void ospf_config_start(void)
++static void ospf_config_start(struct vty *vty)
+ {
+ event_cancel(&t_ospf_cfg);
+ if (IS_DEBUG_OSPF_EVENT)
+@@ -189,7 +189,7 @@ static void ospf_config_start(void)
+ OSPF_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf_cfg);
+ }
+
+-static void ospf_config_end(void)
++static void ospf_config_end(struct vty *vty)
+ {
+ if (IS_DEBUG_OSPF_EVENT)
+ zlog_debug("ospfd config end callback received.");
+diff --git a/sharpd/sharp_main.c b/sharpd/sharp_main.c
+index 2374070102..76aa923e09 100644
+--- a/sharpd/sharp_main.c
++++ b/sharpd/sharp_main.c
+@@ -151,12 +151,12 @@ FRR_DAEMON_INFO(sharpd, SHARP,
+ );
+ /* clang-format on */
+
+-static void sharp_start_configuration(void)
++static void sharp_start_configuration(struct vty *vty)
+ {
+ zlog_debug("Configuration has started to be read");
+ }
+
+-static void sharp_end_configuration(void)
++static void sharp_end_configuration(struct vty *vty)
+ {
+ zlog_debug("Configuration has finished being read");
+ }
+diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl
+index 63c1f60b4f..7060334c4c 100755
+--- a/tools/checkpatch.pl
++++ b/tools/checkpatch.pl
+@@ -563,7 +563,7 @@ our $Iterators = qr{
+ SUBGRP_FOREACH_PEER|SUBGRP_FOREACH_PEER_SAFE|
+ SUBGRP_FOREACH_ADJ|SUBGRP_FOREACH_ADJ_SAFE|
+ AF_FOREACH|FOREACH_AFI_SAFI|FOREACH_SAFI|
+- FOREACH_BE_CLIENT_BITS|FOREACH_MGMTD_BE_CLIENT_ID|
++ FOREACH_BE_CLIENT_BITS|FOREACH_MGMTD_BE_CLIENT_ID|FOREACH_BE_ADAPTER_BITS|
+ FOREACH_SESSION_IN_LIST|
+ LSDB_LOOP
+ }x;
+diff --git a/watchfrr/watchfrr.c b/watchfrr/watchfrr.c
+index 62c6a1f42c..6bfbd47950 100644
+--- a/watchfrr/watchfrr.c
++++ b/watchfrr/watchfrr.c
+@@ -1275,12 +1275,12 @@ static FRR_NORETURN void netns_setup(const char *nsname)
+ }
+ #endif
+
+-static void watchfrr_start_config(void)
++static void watchfrr_start_config(struct vty *vty)
+ {
+ gs.reading_configuration = true;
+ }
+
+-static void watchfrr_end_config(void)
++static void watchfrr_end_config(struct vty *vty)
+ {
+ gs.reading_configuration = false;
+ }
+--
+2.50.1
+