T#11633: bbfdm: Support transaction commit/abort for microservices

This commit is contained in:
Amin Ben Romdhane 2023-11-28 14:47:36 +00:00
parent 01c0c3769e
commit f43885b47d
8 changed files with 138 additions and 30 deletions

View file

@ -562,6 +562,7 @@ int bbfdm_set_handler(struct ubus_context *ctx, struct ubus_object *obj,
blob_buf_init(&data.bb, 0); blob_buf_init(&data.bb, 0);
bbf_init(&data.bbf_ctx); bbf_init(&data.bbf_ctx);
data.ctx = ctx;
data.bbf_ctx.in_param = path; data.bbf_ctx.in_param = path;
fault = fill_pvlist_set(&data, path, tb[DM_SET_VALUE] ? blobmsg_get_string(tb[DM_SET_VALUE]) : NULL, tb[DM_SET_OBJ_PATH], &pv_list); fault = fill_pvlist_set(&data, path, tb[DM_SET_VALUE] ? blobmsg_get_string(tb[DM_SET_VALUE]) : NULL, tb[DM_SET_OBJ_PATH], &pv_list);
@ -584,11 +585,13 @@ int bbfdm_set_handler(struct ubus_context *ctx, struct ubus_object *obj,
WARNING("Transaction not started yet"); WARNING("Transaction not started yet");
fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR);
goto end; goto end;
} else {
data.bbf_ctx.trans_id = data.trans_id;
} }
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Transaction-id is not defined so create an internal transaction // Transaction-id is not defined so create an internal transaction
trans_id = transaction_start("INT_SET", 0); trans_id = transaction_start(&data, "INT_SET", 0);
if (trans_id == 0) { if (trans_id == 0) {
WARNING("Failed to get the lock for the transaction"); WARNING("Failed to get the lock for the transaction");
fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR);
@ -600,7 +603,7 @@ int bbfdm_set_handler(struct ubus_context *ctx, struct ubus_object *obj,
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Internal transaction: need to commit the changes // Internal transaction: need to commit the changes
transaction_commit(trans_id, NULL, true); transaction_commit(NULL, trans_id, true);
} }
end: end:
@ -687,6 +690,7 @@ int bbfdm_add_handler(struct ubus_context *ctx, struct ubus_object *obj,
snprintf(path, PATH_MAX, "%s", (char *)blobmsg_data(tb[DM_ADD_PATH])); snprintf(path, PATH_MAX, "%s", (char *)blobmsg_data(tb[DM_ADD_PATH]));
data.ctx = ctx;
data.bbf_ctx.in_param = path; data.bbf_ctx.in_param = path;
fill_optional_data(&data, tb[DM_ADD_OPTIONAL]); fill_optional_data(&data, tb[DM_ADD_OPTIONAL]);
@ -701,11 +705,13 @@ int bbfdm_add_handler(struct ubus_context *ctx, struct ubus_object *obj,
WARNING("Transaction not started yet"); WARNING("Transaction not started yet");
fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR);
goto end; goto end;
} else {
data.bbf_ctx.trans_id = data.trans_id;
} }
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Transaction-id is not defined so create an internal transaction // Transaction-id is not defined so create an internal transaction
trans_id = transaction_start("INT_ADD", 0); trans_id = transaction_start(&data, "INT_ADD", 0);
if (trans_id == 0) { if (trans_id == 0) {
ERR("Failed to get the lock for the transaction"); ERR("Failed to get the lock for the transaction");
fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR);
@ -719,7 +725,7 @@ int bbfdm_add_handler(struct ubus_context *ctx, struct ubus_object *obj,
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Internal transaction: need to abort the changes // Internal transaction: need to abort the changes
transaction_abort(trans_id, NULL); transaction_abort(NULL, trans_id);
} }
goto end; goto end;
@ -737,7 +743,7 @@ int bbfdm_add_handler(struct ubus_context *ctx, struct ubus_object *obj,
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Internal transaction: need to abort the changes // Internal transaction: need to abort the changes
transaction_abort(trans_id, NULL); transaction_abort(NULL, trans_id);
} }
free_pv_list(&pv_list); free_pv_list(&pv_list);
@ -753,7 +759,7 @@ int bbfdm_add_handler(struct ubus_context *ctx, struct ubus_object *obj,
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Internal transaction: need to commit the changes // Internal transaction: need to commit the changes
transaction_commit(trans_id, NULL, true); transaction_commit(NULL, trans_id, true);
} }
end: end:
@ -805,6 +811,7 @@ int bbfdm_del_handler(struct ubus_context *ctx, struct ubus_object *obj,
} }
} }
data.ctx = ctx;
data.plist = &paths_list; data.plist = &paths_list;
fill_optional_data(&data, tb[DM_DEL_OPTIONAL]); fill_optional_data(&data, tb[DM_DEL_OPTIONAL]);
@ -821,11 +828,13 @@ int bbfdm_del_handler(struct ubus_context *ctx, struct ubus_object *obj,
WARNING("Transaction not started yet"); WARNING("Transaction not started yet");
fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR);
goto end; goto end;
} else {
data.bbf_ctx.trans_id = data.trans_id;
} }
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Transaction-id is not defined so create an internal transaction // Transaction-id is not defined so create an internal transaction
trans_id = transaction_start("INT_DEL", 0); trans_id = transaction_start(&data, "INT_DEL", 0);
if (trans_id == 0) { if (trans_id == 0) {
WARNING("Failed to get the lock for the transaction"); WARNING("Failed to get the lock for the transaction");
fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR);
@ -837,7 +846,7 @@ int bbfdm_del_handler(struct ubus_context *ctx, struct ubus_object *obj,
if (data.trans_id == 0) { if (data.trans_id == 0) {
// Internal transaction: need to commit the changes // Internal transaction: need to commit the changes
transaction_commit(trans_id, NULL, true); transaction_commit(NULL, trans_id, true);
} }
end: end:
@ -901,9 +910,11 @@ static int bbfdm_transaction_handler(struct ubus_context *ctx, struct ubus_objec
bbf_init(&data.bbf_ctx); bbf_init(&data.bbf_ctx);
blob_buf_init(&data.bb, 0); blob_buf_init(&data.bb, 0);
data.ctx = ctx;
if (is_str_eq(trans_cmd, "start")) { if (is_str_eq(trans_cmd, "start")) {
cancel_periodic_timers(ctx); cancel_periodic_timers(ctx);
ret = transaction_start("API", max_timeout); ret = transaction_start(&data, "API", max_timeout);
if (ret) { if (ret) {
blobmsg_add_u8(&data.bb, "status", true); blobmsg_add_u8(&data.bb, "status", true);
blobmsg_add_u32(&data.bb, "transaction_id", ret); blobmsg_add_u32(&data.bb, "transaction_id", ret);
@ -913,11 +924,11 @@ static int bbfdm_transaction_handler(struct ubus_context *ctx, struct ubus_objec
} }
} else if (is_str_eq(trans_cmd, "commit")) { } else if (is_str_eq(trans_cmd, "commit")) {
register_periodic_timers(ctx); register_periodic_timers(ctx);
ret = transaction_commit(data.trans_id, &data.bb, is_service_restart); ret = transaction_commit(&data, data.trans_id, is_service_restart);
blobmsg_add_u8(&data.bb, "status", (ret == 0)); blobmsg_add_u8(&data.bb, "status", (ret == 0));
} else if (is_str_eq(trans_cmd, "abort")) { } else if (is_str_eq(trans_cmd, "abort")) {
register_periodic_timers(ctx); register_periodic_timers(ctx);
ret = transaction_abort(data.trans_id, &data.bb); ret = transaction_abort(&data, data.trans_id);
blobmsg_add_u8(&data.bb, "status", (ret == 0)); blobmsg_add_u8(&data.bb, "status", (ret == 0));
} else if (is_str_eq(trans_cmd, "status")) { } else if (is_str_eq(trans_cmd, "status")) {
transaction_status(&data.bb); transaction_status(&data.bb);

View file

@ -18,6 +18,8 @@
#include "common.h" #include "common.h"
#include "pretty_print.h" #include "pretty_print.h"
extern struct list_head head_registered_service;
DMOBJ *DEAMON_DM_ROOT_OBJ = NULL; DMOBJ *DEAMON_DM_ROOT_OBJ = NULL;
DM_MAP_VENDOR *DEAMON_DM_VENDOR_EXTENSION[2] = {0}; DM_MAP_VENDOR *DEAMON_DM_VENDOR_EXTENSION[2] = {0};
DM_MAP_VENDOR_EXCLUDE *DEAMON_DM_VENDOR_EXTENSION_EXCLUDE = NULL; DM_MAP_VENDOR_EXCLUDE *DEAMON_DM_VENDOR_EXTENSION_EXCLUDE = NULL;
@ -187,7 +189,7 @@ void fill_err_code_array(bbfdm_data_t *data, int fault)
static void transaction_timeout_handler(struct uloop_timeout *t __attribute__((unused))) static void transaction_timeout_handler(struct uloop_timeout *t __attribute__((unused)))
{ {
INFO("Transaction timeout called, aborting tid %d", g_current_trans.trans_id); INFO("Transaction timeout called, aborting tid %d", g_current_trans.trans_id);
transaction_abort(g_current_trans.trans_id, NULL); transaction_abort(NULL, g_current_trans.trans_id);
} }
static int get_random_id(void) static int get_random_id(void)
@ -269,7 +271,7 @@ static int compare_path(const void *arg1, const void *arg2)
} }
// Returns transaction id if successful, otherwise 0 // Returns transaction id if successful, otherwise 0
int transaction_start(char *app, uint32_t max_timeout) int transaction_start(bbfdm_data_t *data, char *app, uint32_t max_timeout)
{ {
int ret = 0; int ret = 0;
uint32_t timeout; uint32_t timeout;
@ -280,12 +282,12 @@ int transaction_start(char *app, uint32_t max_timeout)
} }
if (max_timeout > 0) { if (max_timeout > 0) {
timeout = max_timeout; timeout = max_timeout * 1000;
} else { } else {
timeout = g_current_trans.timeout_ms; timeout = g_current_trans.timeout_ms;
} }
ret = get_random_id(); ret = data->trans_id ? data->trans_id : get_random_id();
strncpyt(g_current_trans.app, app, 32); strncpyt(g_current_trans.app, app, 32);
g_current_trans.trans_id = ret; g_current_trans.trans_id = ret;
@ -293,6 +295,11 @@ int transaction_start(char *app, uint32_t max_timeout)
uloop_timeout_set(&g_current_trans.trans_timeout, timeout); uloop_timeout_set(&g_current_trans.trans_timeout, timeout);
INFO("Transaction created by [%s] id %d, timeout %zd", g_current_trans.app, g_current_trans.trans_id, timeout); INFO("Transaction created by [%s] id %d, timeout %zd", g_current_trans.app, g_current_trans.trans_id, timeout);
if (data->trans_id) {
// Call transaction for registered services only if transaction id is defined
handle_transaction_of_registered_service(data->ctx, NULL, &head_registered_service, "start", ret, timeout/1000, 0);
}
return ret; return ret;
} }
@ -323,17 +330,26 @@ bool is_transaction_valid(int trans_id)
return (trans_id == g_current_trans.trans_id); return (trans_id == g_current_trans.trans_id);
} }
int transaction_commit(int trans_id, struct blob_buf *bb, bool is_service_restart) int transaction_commit(bbfdm_data_t *data, int trans_id, bool is_service_restart)
{ {
int ret = -1; int ret = -1;
if (is_transaction_valid(trans_id)) { if (is_transaction_valid(trans_id)) {
struct blob_buf *bb = data ? &data->bb : NULL;
void *arr = NULL;
INFO("Commit on-going transaction by %s", g_current_trans.app); INFO("Commit on-going transaction by %s", g_current_trans.app);
uloop_timeout_cancel(&g_current_trans.trans_timeout); uloop_timeout_cancel(&g_current_trans.trans_timeout);
g_current_trans.trans_id = 0; g_current_trans.trans_id = 0;
g_current_trans.app[0] = '\0'; g_current_trans.app[0] = '\0';
if (bb) arr = blobmsg_open_array(bb, "updated_services");
bbf_entry_restart_services(bb, is_service_restart); bbf_entry_restart_services(bb, is_service_restart);
if (data && data->trans_id) {
// Call transaction for registered services only if transaction id is defined
handle_transaction_of_registered_service(data->ctx, bb, &head_registered_service, "commit", data->trans_id, 0, is_service_restart);
}
if (bb) blobmsg_close_array(bb, arr);
ret = 0; ret = 0;
} else { } else {
@ -343,17 +359,26 @@ int transaction_commit(int trans_id, struct blob_buf *bb, bool is_service_restar
return ret; return ret;
} }
int transaction_abort(int trans_id, struct blob_buf *bb) int transaction_abort(bbfdm_data_t *data, int trans_id)
{ {
int ret = -1; int ret = -1;
if (is_transaction_valid(trans_id)) { if (is_transaction_valid(trans_id)) {
struct blob_buf *bb = data ? &data->bb : NULL;
void *arr = NULL;
INFO("Abort on-going transaction by %s", g_current_trans.app); INFO("Abort on-going transaction by %s", g_current_trans.app);
uloop_timeout_cancel(&g_current_trans.trans_timeout); uloop_timeout_cancel(&g_current_trans.trans_timeout);
g_current_trans.trans_id = 0; g_current_trans.trans_id = 0;
g_current_trans.app[0] = '\0'; g_current_trans.app[0] = '\0';
if (bb) arr = blobmsg_open_array(bb, "reverted_configs");
bbf_entry_revert_changes(bb); bbf_entry_revert_changes(bb);
if (data && data->trans_id) {
// Call transaction for registered services only if transaction id is defined
handle_transaction_of_registered_service(data->ctx, bb, &head_registered_service, "abort", data->trans_id, 0, 0);
}
if (bb) blobmsg_close_array(bb, arr);
ret = 0; ret = 0;
} else { } else {

View file

@ -40,9 +40,9 @@ void fill_err_code_array(bbfdm_data_t *data, int fault);
void bb_add_string(struct blob_buf *bb, const char *name, const char *value); void bb_add_string(struct blob_buf *bb, const char *name, const char *value);
int transaction_start(char *app, uint32_t max_timeout); int transaction_start(bbfdm_data_t *data, char *app, uint32_t max_timeout);
int transaction_commit(int trans_id, struct blob_buf *bb, bool is_service_restart); int transaction_commit(bbfdm_data_t *data, int trans_id, bool is_service_restart);
int transaction_abort(int trans_id, struct blob_buf *bb); int transaction_abort(bbfdm_data_t *data, int trans_id);
int transaction_status(struct blob_buf *bb); int transaction_status(struct blob_buf *bb);
bool is_transaction_running(void); bool is_transaction_running(void);
bool is_transaction_valid(int trans_id); bool is_transaction_valid(int trans_id);

View file

@ -206,6 +206,7 @@ struct dmctx {
bool isevent; bool isevent;
bool isinfo; bool isinfo;
bool disable_mservice_browse; bool disable_mservice_browse;
int trans_id;
}; };
typedef struct dmnode { typedef struct dmnode {

View file

@ -1291,6 +1291,7 @@ static int add_ubus_object(struct dmctx *dmctx, struct dmnode *node)
json_object_object_add(in_args, "proto", json_object_new_string((dmctx->dm_type == BBFDM_BOTH) ? "both" : (dmctx->dm_type == BBFDM_CWMP) ? "cwmp" : "usp")); json_object_object_add(in_args, "proto", json_object_new_string((dmctx->dm_type == BBFDM_BOTH) ? "both" : (dmctx->dm_type == BBFDM_CWMP) ? "cwmp" : "usp"));
json_object_object_add(in_args, "instance_mode", json_object_new_string(dmctx->instance_mode ? "1" : "0")); json_object_object_add(in_args, "instance_mode", json_object_new_string(dmctx->instance_mode ? "1" : "0"));
json_object_object_add(in_args, "format", json_object_new_string("raw")); json_object_object_add(in_args, "format", json_object_new_string("raw"));
json_object_object_add(in_args, "transaction_id", json_object_new_int(dmctx->trans_id));
dmubus_call(ubus_name, "add", dmubus_call(ubus_name, "add",
UBUS_ARGS{ UBUS_ARGS{
@ -1337,6 +1338,7 @@ static int del_ubus_object(struct dmctx *dmctx, struct dmnode *node)
json_object_object_add(in_args, "proto", json_object_new_string((dmctx->dm_type == BBFDM_BOTH) ? "both" : (dmctx->dm_type == BBFDM_CWMP) ? "cwmp" : "usp")); json_object_object_add(in_args, "proto", json_object_new_string((dmctx->dm_type == BBFDM_BOTH) ? "both" : (dmctx->dm_type == BBFDM_CWMP) ? "cwmp" : "usp"));
json_object_object_add(in_args, "instance_mode", json_object_new_string(dmctx->instance_mode ? "1" : "0")); json_object_object_add(in_args, "instance_mode", json_object_new_string(dmctx->instance_mode ? "1" : "0"));
json_object_object_add(in_args, "format", json_object_new_string("raw")); json_object_object_add(in_args, "format", json_object_new_string("raw"));
json_object_object_add(in_args, "transaction_id", json_object_new_int(dmctx->trans_id));
dmubus_call(ubus_name, "del", dmubus_call(ubus_name, "del",
UBUS_ARGS{ UBUS_ARGS{
@ -1408,6 +1410,7 @@ static int set_ubus_value(struct dmctx *dmctx, struct dmnode *node)
json_object_object_add(in_args, "proto", json_object_new_string((dmctx->dm_type == BBFDM_BOTH) ? "both" : (dmctx->dm_type == BBFDM_CWMP) ? "cwmp" : "usp")); json_object_object_add(in_args, "proto", json_object_new_string((dmctx->dm_type == BBFDM_BOTH) ? "both" : (dmctx->dm_type == BBFDM_CWMP) ? "cwmp" : "usp"));
json_object_object_add(in_args, "instance_mode", json_object_new_string(dmctx->instance_mode ? "1" : "0")); json_object_object_add(in_args, "instance_mode", json_object_new_string(dmctx->instance_mode ? "1" : "0"));
json_object_object_add(in_args, "format", json_object_new_string("raw")); json_object_object_add(in_args, "format", json_object_new_string("raw"));
json_object_object_add(in_args, "transaction_id", json_object_new_int(dmctx->trans_id));
if (is_reference_parameter(ubus_name, dmctx->in_param, in_args)) { if (is_reference_parameter(ubus_name, dmctx->in_param, in_args)) {
get_reference_paramater_value(dmctx, dmctx->in_value, param_value, sizeof(param_value)); get_reference_paramater_value(dmctx, dmctx->in_value, param_value, sizeof(param_value));

View file

@ -439,9 +439,6 @@ bool adm_entry_object_exists(struct dmctx *ctx, char *param)
void bbf_entry_restart_services(struct blob_buf *bb, bool restart_services) void bbf_entry_restart_services(struct blob_buf *bb, bool restart_services)
{ {
struct package_change *pc = NULL; struct package_change *pc = NULL;
void *arr = NULL;
if (bb) arr = blobmsg_open_array(bb, "updated_services");
list_for_each_entry(pc, &head_package_change, list) { list_for_each_entry(pc, &head_package_change, list) {
@ -454,8 +451,6 @@ void bbf_entry_restart_services(struct blob_buf *bb, bool restart_services)
} }
} }
if (bb) blobmsg_close_array(bb, arr);
dmuci_commit_bbfdm(); dmuci_commit_bbfdm();
free_all_list_package_change(&head_package_change); free_all_list_package_change(&head_package_change);
@ -464,9 +459,6 @@ void bbf_entry_restart_services(struct blob_buf *bb, bool restart_services)
void bbf_entry_revert_changes(struct blob_buf *bb) void bbf_entry_revert_changes(struct blob_buf *bb)
{ {
struct package_change *pc = NULL; struct package_change *pc = NULL;
void *arr = NULL;
if (bb) arr = blobmsg_open_array(bb, "reverted_configs");
list_for_each_entry(pc, &head_package_change, list) { list_for_each_entry(pc, &head_package_change, list) {
@ -475,8 +467,6 @@ void bbf_entry_revert_changes(struct blob_buf *bb)
dmubus_call_set("uci", "revert", UBUS_ARGS{{"config", pc->package, String}}, 1); dmubus_call_set("uci", "revert", UBUS_ARGS{{"config", pc->package, String}}, 1);
} }
if (bb) blobmsg_close_array(bb, arr);
dmuci_revert_bbfdm(); dmuci_revert_bbfdm();
free_all_list_package_change(&head_package_change); free_all_list_package_change(&head_package_change);

View file

@ -48,4 +48,7 @@ void get_list_of_registered_service(struct list_head *srvlist, struct blob_buf *
bool load_service(DMOBJ *main_dm, struct list_head *srv_list, char *srv_name, char *srv_parent_dm, char *srv_obj); bool load_service(DMOBJ *main_dm, struct list_head *srv_list, char *srv_name, char *srv_parent_dm, char *srv_obj);
void free_services_from_list(struct list_head *clist); void free_services_from_list(struct list_head *clist);
int handle_transaction_of_registered_service(struct ubus_context *ctx, struct blob_buf *trans_bb, struct list_head *srvlist,
const char *trans_cmd, int trans_id, uint32_t max_timeout, bool service_restart);
#endif //__DMENTRY_H__ #endif //__DMENTRY_H__

View file

@ -134,6 +134,81 @@ bool load_service(DMOBJ *main_dm, struct list_head *srv_list, char *srv_name, ch
return true; return true;
} }
static void ubus_transaction_callback(struct ubus_request *req, int type __attribute__((unused)), struct blob_attr *msg)
{
struct blob_attr *tb[2] = {0};
const struct blobmsg_policy p[2] = {
{ "updated_services", BLOBMSG_TYPE_ARRAY },
{ "reverted_configs", BLOBMSG_TYPE_ARRAY }
};
if (msg == NULL || req == NULL)
return;
struct blob_buf *bb = (struct blob_buf *)req->priv;
if (bb == NULL)
return;
blobmsg_parse(p, 2, tb, blobmsg_data(msg), blobmsg_len(msg));
if (tb[0]) {
struct blob_attr *service = NULL;
size_t rem;
blobmsg_for_each_attr(service, tb[0], rem) {
blobmsg_add_string(bb, NULL, blobmsg_get_string(service));
}
}
if (tb[1]) {
struct blob_attr *config = NULL;
size_t rem;
blobmsg_for_each_attr(config, tb[1], rem) {
blobmsg_add_string(bb, NULL, blobmsg_get_string(config));
}
}
}
int handle_transaction_of_registered_service(struct ubus_context *ctx, struct blob_buf *trans_bb, struct list_head *srvlist,
const char *trans_cmd, int trans_id, uint32_t max_timeout, bool service_restart)
{
struct service *srv = NULL;
if (is_micro_service == true) // This should be called only from main daemon
return -1;
if (ctx == NULL || trans_id == 0)
return -1;
list_for_each_entry(srv, srvlist, list) {
struct blob_buf bb = {0};
void *table = NULL;
uint32_t ubus_id;
// check if object already present
int ret = ubus_lookup_id(ctx, srv->name, &ubus_id);
if (ret != 0)
continue;
memset(&bb, 0, sizeof(struct blob_buf));
blob_buf_init(&bb, 0);
blobmsg_add_string(&bb, "cmd", trans_cmd);
blobmsg_add_u8(&bb, "restart_services", service_restart);
blobmsg_add_u32(&bb, "timeout", max_timeout);
table = blobmsg_open_table(&bb, "optional");
blobmsg_add_u32(&bb, "transaction_id", trans_id);
blobmsg_close_table(&bb, table);
ubus_invoke(ctx, ubus_id, "transaction", bb.head, ubus_transaction_callback, (void *)trans_bb, 5000);
blob_buf_free(&bb);
}
return 0;
}
void get_list_of_registered_service(struct list_head *srvlist, struct blob_buf *bb) void get_list_of_registered_service(struct list_head *srvlist, struct blob_buf *bb)
{ {
struct service *srv = NULL; struct service *srv = NULL;