/* * bbf_config.c: bbf.config daemon * * Copyright (C) 2024 IOPSYS Software Solutions AB. All rights reserved. * * Author: Amin Ben Romdhane * * See LICENSE file for license related information. */ #include #include #include #include #include #include #include #include #include "utils.h" struct trans_ctx { struct ubus_context *ctx; const char *cmd; /* "commit" or "abort" */ const char *proto; /* proto from commit/revert input */ }; /* ------------------------------------------------------------------ */ /* DM-framework service transaction helpers */ /* ------------------------------------------------------------------ */ static void invoke_service_transaction(struct ubus_context *ctx, const char *service_name, const char *cmd, const char *proto) { if (!ctx || !service_name || !cmd) return; struct blob_buf bb = {0}; blob_buf_init(&bb, 0); blobmsg_add_string(&bb, "cmd", cmd); if (proto && strlen(proto)) { void *tbl = blobmsg_open_table(&bb, "optional"); blobmsg_add_string(&bb, "proto", proto); blobmsg_close_table(&bb, tbl); } if (bbf_config_call(ctx, service_name, "transaction", &bb, NULL, NULL)) { ULOG_ERR("Failed '%s' transaction for service '%s'", cmd, service_name); } else { ULOG_INFO("Service '%s' transaction '%s' succeeded", service_name, cmd); } blob_buf_free(&bb); } /* Callback used when querying bbfdm 'services' */ static void services_list_cb(struct ubus_request *req, int type __attribute__((unused)), struct blob_attr *msg) { struct trans_ctx *tctx = (struct trans_ctx *)req->priv; if (!tctx || !msg) return; /* Expecting { "registered_services": [ {...}, ... ] } */ struct blob_attr *rs_array = NULL; struct blob_attr *cur; int rem = blob_len(msg); /* Find the array first */ blob_for_each_attr(cur, msg, rem) { if (blobmsg_type(cur) == BLOBMSG_TYPE_ARRAY && strcmp(blobmsg_name(cur), "registered_services") == 0) { rs_array = cur; break; } } if (!rs_array) return; const struct blobmsg_policy pol[] = { { "name", BLOBMSG_TYPE_STRING }, { "proto", BLOBMSG_TYPE_STRING }, }; struct blob_attr *entry; blobmsg_for_each_attr(entry, rs_array, rem) { struct blob_attr *tb[2] = {0}; blobmsg_parse(pol, 2, tb, blobmsg_data(entry), blobmsg_len(entry)); if (!tb[0]) continue; const char *sname = blobmsg_get_string(tb[0]); const char *proto = (tctx->proto) ? tctx->proto : "both"; ULOG_ERR("Invoking service transaction for service: %s, cmd: %s, proto: %s", sname, tctx->cmd, proto); invoke_service_transaction(tctx->ctx, sname, tctx->cmd, proto); } } static void trigger_dfm_service_transactions(struct ubus_context *ctx, const char *cmd, const char *proto) { if (!ctx || !cmd) return; ULOG_ERR("Triggering dm-framework service transactions cmd: %s, proto: %s", cmd, proto); struct blob_buf bb = {0}; blob_buf_init(&bb, 0); blobmsg_add_u8(&bb, "dmf_only", true); struct trans_ctx tctx = { .ctx = ctx, .cmd = cmd, .proto = proto, }; /* Query bbfdm -> services */ if (bbf_config_call(ctx, "bbfdm", "services", &bb, services_list_cb, &tctx)) { ULOG_ERR("Failed to retrieve dm-framework services list from bbfdm"); } blob_buf_free(&bb); } #define TIME_TO_WAIT_FOR_RELOAD 5 #define MAX_PACKAGE_NUM 256 #define MAX_SERVICE_NUM 16 #define MAX_INSTANCE_NUM 8 #define NAME_LENGTH 64 #define DEFAULT_LOG_LEVEL LOG_INFO #define BBF_CONFIG_DAEMON_NAME "bbf_configd" #define CRITICAL_DEF_JSON "/etc/bbfdm/critical_services.json" #define BBFDM_MICROSERVICE_INPUT_PATH "/etc/bbfdm/services" static struct list_head g_external_changed_uci; // Structure to represent an instance of a service struct instance { char name[NAME_LENGTH]; uint32_t pid; bool is_running; }; // Structure to represent a service struct service { char name[NAME_LENGTH]; bool has_instances; struct instance instances[MAX_INSTANCE_NUM]; }; // Structure to represent a configuration package struct config_package { char name[NAME_LENGTH]; struct service services[MAX_SERVICE_NUM]; }; struct bbf_config_async_req { int idx; struct ubus_context *ctx; struct ubus_request_data req; struct uloop_timeout timeout; struct blob_attr *services; struct config_package package[MAX_PACKAGE_NUM]; struct list_head changed_uci_list; }; static struct blob_buf g_critical_bb; static struct list_head g_apply_handlers; #ifdef BBF_CONFIG_DEBUG static void log_instance(struct instance *inst) { ULOG_ERR(" |- Instance name: '%s', PID: %d, Status: %s", inst->name, inst->pid, inst->is_running ? "running" : "stopped"); } static void log_service(struct service *svc) { ULOG_ERR(" - Service name: '%s'", svc->name); if (svc->has_instances) { bool has_any_instance = false; for (int i_idx = 0; i_idx < MAX_INSTANCE_NUM; i_idx++) { if (svc->instances[i_idx].name[0] == '\0') break; log_instance(&svc->instances[i_idx]); has_any_instance = true; } if (!has_any_instance) { ULOG_ERR(" |- No active instances"); } } else { ULOG_ERR(" |- No instances available"); } } static void show_package_tree(struct config_package *packages) { for (int p_idx = 0; p_idx < MAX_PACKAGE_NUM; p_idx++) { if (packages[p_idx].name[0] == '\0') break; ULOG_ERR("Package name: '%s'", packages[p_idx].name); bool has_any_service = false; for (int s_idx = 0; s_idx < MAX_SERVICE_NUM; s_idx++) { if (packages[p_idx].services[s_idx].name[0] == '\0') break; log_service(&packages[p_idx].services[s_idx]); has_any_service = true; } if (!has_any_service) { ULOG_ERR(" |- No services defined"); } } } #endif static bool g_internal_commit = false; enum { SERVICES_NAME, SERVICES_PROTO, SERVICES_RELOAD, __MAX }; static const struct blobmsg_policy bbf_config_policy[] = { [SERVICES_NAME] = { .name = "services", .type = BLOBMSG_TYPE_ARRAY }, [SERVICES_PROTO] = { .name = "proto", .type = BLOBMSG_TYPE_STRING }, [SERVICES_RELOAD] = { .name = "reload", .type = BLOBMSG_TYPE_BOOL }, }; static int find_config_idx(struct config_package *package, const char *config_name) { if (!config_name) return -1; for (int i = 0; i < MAX_PACKAGE_NUM; i++) { if (strlen(package[i].name) == 0) return -1; if (strcmp(package[i].name, config_name) == 0) return i; } return -1; } static int find_service_idx(struct service *services) { if (!services) return -1; for (int i = 0; i < MAX_SERVICE_NUM; i++) { if (strlen(services[i].name) == 0) return i; } return -1; } static int find_instance_idx(struct instance *instances, const char *instance_name) { if (!instances) return -1; for (int i = 0; i < MAX_INSTANCE_NUM; i++) { if (instance_name && strcmp(instances[i].name, instance_name) == 0) return i; if (strlen(instances[i].name) == 0) return i; } return -1; } static int handle_instances_service(const char *service_name, struct blob_attr *instances, struct config_package *package, unsigned int pkg_idx) { int srv_idx = find_service_idx(package[pkg_idx].services); if (srv_idx < 0) { // Returns if the number of services more than MAX_SERVICE_NUM ULOG_ERR("Failed to handle instance service: service count exceeds MAX_SERVICE_NUM"); return -1; } strncpyt(package[pkg_idx].services[srv_idx].name, service_name, NAME_LENGTH); package[pkg_idx].services[srv_idx].has_instances = (instances) ? true : false; if (!instances) return -1; struct blob_attr *cur; int rem; int inst_idx = find_instance_idx(package[pkg_idx].services[srv_idx].instances, NULL); if (inst_idx < 0) // Returns if the number of instances more than MAX_INSTANCE_NUM return -1; blobmsg_for_each_attr(cur, instances, rem) { struct blob_attr *tb[2] = {0}; const struct blobmsg_policy p[2] = { { "running", BLOBMSG_TYPE_BOOL }, { "pid", BLOBMSG_TYPE_INT32 } }; blobmsg_parse(p, 2, tb, blobmsg_data(cur), blobmsg_len(cur)); strncpyt(package[pkg_idx].services[srv_idx].instances[inst_idx].name, blobmsg_name(cur), NAME_LENGTH); package[pkg_idx].services[srv_idx].instances[inst_idx].is_running = (tb[0]) ? blobmsg_get_bool(tb[0]) : false; package[pkg_idx].services[srv_idx].instances[inst_idx].pid = (tb[1]) ? blobmsg_get_u32(tb[1]) : 0; inst_idx++; } return 0; } static int handle_triggers_service(const char *service_name, struct blob_attr *triggers, struct blob_attr *instances, struct config_package *package, unsigned int *index) { struct blob_attr *cur; int rem; blobmsg_for_each_attr(cur, triggers, rem) { struct blob_attr *_cur, *type = NULL, *script = NULL, *config = NULL, *name = NULL; size_t _rem; int i = 0; if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY) continue; blobmsg_for_each_attr(_cur, cur, _rem) { switch (i++) { case 0: if (blobmsg_type(_cur) == BLOBMSG_TYPE_STRING) type = _cur; break; case 1: if (blobmsg_type(_cur) == BLOBMSG_TYPE_ARRAY) script = _cur; break; } } if (!type || !script || strcmp(blobmsg_get_string(type), "config.change") != 0) continue; type = NULL; i = 0; blobmsg_for_each_attr(_cur, script, _rem) { switch (i++) { case 0: if (blobmsg_type(_cur) == BLOBMSG_TYPE_STRING) type = _cur; break; case 1: if (blobmsg_type(_cur) == BLOBMSG_TYPE_ARRAY) config = _cur; break; } } if (!type || !config || strcmp(blobmsg_get_string(type), "if") != 0) continue; type = NULL; script = NULL; i = 0; blobmsg_for_each_attr(_cur, config, _rem) { switch (i++) { case 0: if (blobmsg_type(_cur) == BLOBMSG_TYPE_STRING) type = _cur; break; case 1: if (blobmsg_type(_cur) == BLOBMSG_TYPE_STRING) script = _cur; break; case 2: if (blobmsg_type(_cur) == BLOBMSG_TYPE_STRING) name = _cur; break; } } if (!type || !script || !name || strcmp(blobmsg_get_string(type), "eq") != 0 || strcmp(blobmsg_get_string(script), "package") != 0) continue; char *config_name = blobmsg_get_string(name); int config_idx = find_config_idx(package, config_name); unsigned int pkg_idx = (config_idx < 0) ? (*index)++ : config_idx; strncpyt(package[pkg_idx].name, blobmsg_get_string(name), NAME_LENGTH); handle_instances_service(service_name, instances, package, pkg_idx); } return 0; } static void _get_service_list_cb(struct ubus_request *req, int type, struct blob_attr *msg) { struct blob_attr *cur; struct blob_attr *tb[2] = {0}; const struct blobmsg_policy p[2] = { { "triggers", BLOBMSG_TYPE_ARRAY }, { "instances", BLOBMSG_TYPE_TABLE } }; size_t rem; unsigned int idx = 0; if (!msg || !req) { ULOG_ERR("Cannot proceed: 'msg' or 'req' is NULL."); return; } struct config_package *package = (struct config_package *)req->priv; blobmsg_for_each_attr(cur, msg, rem) { blobmsg_parse(p, 2, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[0]) continue; handle_triggers_service(blobmsg_name(cur), tb[0], tb[1], package, &idx); } } static void _get_specific_service_cb(struct ubus_request *req, int type, struct blob_attr *msg) { struct blob_attr *cur; struct blob_attr *tb[1] = {0}; const struct blobmsg_policy p[1] = { { "instances", BLOBMSG_TYPE_TABLE } }; size_t rem; if (!msg || !req) { ULOG_ERR("Cannot proceed: 'msg' or 'req' is NULL."); return; } struct config_package *package = (struct config_package *)req->priv; blobmsg_for_each_attr(cur, msg, rem) { blobmsg_parse(p, 1, tb, blobmsg_data(cur), blobmsg_len(cur)); handle_instances_service(blobmsg_name(cur), tb[0], package, 0); } } static void fill_service_info(struct ubus_context *ctx, struct config_package *package, const char *name, bool verbose, ubus_data_handler_t callback) { struct blob_buf ubus_bb = {0}; memset(&ubus_bb, 0 , sizeof(struct blob_buf)); blob_buf_init(&ubus_bb, 0); if (name) blobmsg_add_string(&ubus_bb, "name", name); blobmsg_add_u8(&ubus_bb, "verbose", verbose); bbf_config_call(ctx, "service", "list", &ubus_bb, callback, (void *)package); blob_buf_free(&ubus_bb); } static bool validate_required_services(struct ubus_context *ctx, struct config_package *package, struct blob_attr *services) { struct blob_attr *service = NULL; size_t rem = 0; if (!services || !package) return true; // Iterate through each service attribute blobmsg_for_each_attr(service, services, rem) { char *config_name = blobmsg_get_string(service); char *p = strrchr(config_name, '/'); if (p) { p = p + 1; } // Find the index of the configuration package int idx = find_config_idx(package, p ? p : config_name); if (idx < 0) continue; for (int j = 0; j < MAX_SERVICE_NUM && strlen(package[idx].services[j].name); j++) { if (strcmp(package[idx].services[j].name, BBF_CONFIG_DAEMON_NAME) == 0) { // Skip 'bbf_configd' service itself, as it does not need processing here continue; // Move to the next service } // Get configuration information for each service name struct config_package new_package[1] = {0}; memset(new_package, 0, sizeof(struct config_package)); fill_service_info(ctx, new_package, package[idx].services[j].name, false, _get_specific_service_cb); if (package[idx].services[j].has_instances != new_package[0].services[0].has_instances) { // If the number of instances has changed, the service is correctly updated continue; // Move to the next service } if (package[idx].services[j].has_instances == 0) { // No instances to check, unsure if service is correctly updated goto wait; } for (int t = 0; t < MAX_SERVICE_NUM && strlen(package[idx].services[j].instances[t].name); t++) { // Find the index of the instance in the new package int inst_idx = find_instance_idx(new_package[0].services[0].instances, package[idx].services[j].instances[t].name); if (inst_idx < 0) { // Instance doesn't exist after reload, indicating a disabled instance continue; // Move to the next service } if (package[idx].services[j].instances[t].is_running != new_package[0].services[0].instances[inst_idx].is_running) { // Instance status changed after reload, service correctly updated continue; // Move to the next service } if (package[idx].services[j].instances[t].pid != new_package[0].services[0].instances[inst_idx].pid) { // Instance PID changed after reload, service correctly updated continue; // Move to the next service } // Wait for a sufficient time to ensure services are reloaded goto wait; } } } return true; wait: return false; } static void send_bbf_apply_event(int idx, struct list_head *changed_uci_list) { char protocol[16] = {0}; if (changed_uci_list == NULL || list_empty(changed_uci_list)) return; struct ubus_context *ctx; struct blob_buf bb = {0}; struct modi_uci_node *node = NULL, *tmp = NULL; memset(&bb, 0, sizeof(struct blob_buf)); blob_buf_init(&bb, 0); snprintf(protocol, sizeof(protocol), "%s", (idx == -1) ? "external" : get_proto_name_by_idx(idx)); blobmsg_add_string(&bb, "proto", protocol); void *array = blobmsg_open_array(&bb, "uci_changed"); list_for_each_entry_safe(node, tmp, changed_uci_list, list) { if (node->uci == NULL) { list_del(&node->list); FREE(node); continue; } blobmsg_add_string(&bb, NULL, node->uci); list_del(&node->list); FREE(node->uci); FREE(node); } blobmsg_close_array(&bb, array); ctx = ubus_connect(NULL); if (ctx == NULL) { ULOG_ERR("Can't create UBUS context for 'bbfdm.apply' event"); blob_buf_free(&bb); return; } ULOG_INFO("Sending bbfdm.apply event"); ubus_send_event(ctx, "bbfdm.apply", bb.head); blob_buf_free(&bb); ubus_free(ctx); } static void send_reply(struct ubus_context *ctx, struct ubus_request_data *req, const char *message, const char *description) { struct blob_buf bb = {0}; memset(&bb, 0, sizeof(struct blob_buf)); blob_buf_init(&bb, 0); blobmsg_add_string(&bb, message, description); ubus_send_reply(ctx, req, bb.head); blob_buf_free(&bb); } static void complete_deferred_request(struct bbf_config_async_req *async_req) { if (!async_req) return; // Send the response send_reply(async_req->ctx, &async_req->req, "status", "ok"); // Complete the deferred request and send the response ubus_complete_deferred_request(async_req->ctx, &async_req->req, 0); // If any uci is changed externally then add it in bbf.apply event struct modi_uci_node *node = NULL, *tmp = NULL; list_for_each_entry_safe(node, tmp, &g_external_changed_uci, list) { if (node->uci == NULL) { list_del(&node->list); FREE(node); continue; } add_changed_uci_list(&async_req->changed_uci_list, node->uci); list_del(&node->list); FREE(node->uci); FREE(node); } // Send 'bbf.apply' event send_bbf_apply_event(async_req->idx, &async_req->changed_uci_list); // Free the allocated memory FREE(async_req->services); FREE(async_req); // Set internal commit to false g_internal_commit = false; ULOG_INFO("Commit handler exit"); } static void end_request_callback(struct uloop_timeout *t) { struct bbf_config_async_req *async_req = container_of(t, struct bbf_config_async_req, timeout); // Complete the deferred request and send the reply complete_deferred_request(async_req); } static void complete_request_callback(struct uloop_timeout *t) { struct bbf_config_async_req *async_req = container_of(t, struct bbf_config_async_req, timeout); // Check if the required services are really reloaded bool reload_done = validate_required_services(async_req->ctx, async_req->package, async_req->services); if (reload_done) { // Complete the deferred request and send the reply complete_deferred_request(async_req); } else { async_req->timeout.cb = end_request_callback; uloop_timeout_set(&async_req->timeout, TIME_TO_WAIT_FOR_RELOAD * 1000); } } struct blob_attr *get_blob_attr_with_idx(int idx, struct blob_attr *msg) { struct blob_attr *params = NULL; struct blob_attr *cur; int rem; const char *proto = get_proto_name_by_idx(idx); blobmsg_for_each_attr(cur, msg, rem) { const char *name = blobmsg_name(cur); if ((strcmp(proto, name) == 0) && (blobmsg_type(cur) == BLOBMSG_TYPE_ARRAY)) { params = cur; break; } } return params; } static bool check_if_critical_service(int proto_idx, const char *sname) { bool is_critical = false; struct blob_attr *service = NULL, *services_ba; size_t rem = 0; services_ba = get_blob_attr_with_idx(proto_idx, g_critical_bb.head); if (services_ba == NULL) { ULOG_DEBUG("Critical service not defined for %s proto", get_proto_name_by_idx(proto_idx)); return is_critical; } blobmsg_for_each_attr(service, services_ba, rem) { char *config_name = blobmsg_get_string(service); if (strcmp(sname, config_name) == 0) { is_critical = true; break; } } ULOG_DEBUG("Service %s, found %d, in %s critical list", sname, is_critical, get_proto_name_by_idx(proto_idx)); return is_critical; } static bool get_monitor_status(int proto_idx, struct blob_attr *services_ba) { bool monitor = false, is_critical; struct blob_attr *service = NULL; size_t rem = 0; if (services_ba == NULL) { ULOG_DEBUG("Empty service list"); return monitor; } blobmsg_for_each_attr(service, services_ba, rem) { char *config_name = blobmsg_get_string(service); is_critical = check_if_critical_service(proto_idx, config_name); if (is_critical == true) { monitor = true; break; } } return monitor; } static int bbf_config_commit_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg) { struct blob_attr *tb[__MAX]; bool monitor = false, reload = true; unsigned char idx = 0; struct list_head action_list; struct list_head *changed_uci; ULOG_INFO("Commit handler called"); INIT_LIST_HEAD(&action_list); if (blobmsg_parse(bbf_config_policy, __MAX, tb, blob_data(msg), blob_len(msg))) { send_reply(ctx, req, "error", "Failed to parse blob"); return -1; } struct bbf_config_async_req *async_req = calloc(1, sizeof(struct bbf_config_async_req)); if (!async_req) { send_reply(ctx, req, "error", "Failed to allocate bbf config async request"); return -1; } changed_uci = &async_req->changed_uci_list; INIT_LIST_HEAD(changed_uci); // Set internal commit to true g_internal_commit = true; async_req->ctx = ctx; memset(async_req->package, 0, sizeof(struct config_package) * MAX_PACKAGE_NUM); if (tb[SERVICES_PROTO]) { char *proto = blobmsg_get_string(tb[SERVICES_PROTO]); idx = get_idx_by_proto(proto); ULOG_DEBUG("Protocol index determined as %d for protocol '%s'", idx, proto); } if (tb[SERVICES_RELOAD]) { reload = blobmsg_get_bool(tb[SERVICES_RELOAD]); ULOG_DEBUG("Reload flag set to %s.", reload ? "true" : "false"); } monitor = get_monitor_status(idx, tb[SERVICES_NAME]); if (monitor) { ULOG_DEBUG("Retrieving all config information before committing changes"); fill_service_info(ctx, async_req->package, NULL, true, _get_service_list_cb); #ifdef BBF_CONFIG_DEBUG show_package_tree(async_req->package); #endif } trigger_dfm_service_transactions(ctx, "commit", tb[SERVICES_PROTO] ? blobmsg_get_string(tb[SERVICES_PROTO]) : "both"); struct blob_attr *services = tb[SERVICES_NAME]; size_t arr_len = (services) ? blobmsg_len(services) : 0; if (arr_len) { size_t blob_data_len = blob_raw_len(services); if (blob_data_len) { async_req->services = (struct blob_attr *)calloc(1, blob_data_len); if (!async_req->services) { ULOG_ERR("Failed to allocate memory for services blob data."); FREE(async_req); send_reply(ctx, req, "error", "Memory allocation error"); g_internal_commit = false; return -1; } memcpy(async_req->services, services, blob_data_len); } ULOG_INFO("Committing changes for specified services and reloading"); reload_specified_services(ctx, idx, async_req->services, true, reload, &action_list, &g_apply_handlers, changed_uci); } else { ULOG_INFO("Applying changes to dmmap UCI config"); uci_apply_changes_dmmap(idx, true, &action_list, &g_apply_handlers); ULOG_INFO("Committing changes for all services and reloading"); reload_all_services(ctx, idx, true, reload, &action_list, &g_apply_handlers, changed_uci); } struct action_node *node = NULL, *tmp = NULL; list_for_each_entry_safe(node, tmp, &action_list, list) { char cmd[4096] = {0}; unsigned pos = 0; ULOG_INFO("Reloading changes"); if (!file_exists(node->action)) { list_del(&node->list); FREE(node); continue; } pos += snprintf(cmd, sizeof(cmd), "sh %s", node->action); for (int i = 0; i < node->idx; i++) { pos += snprintf(&cmd[pos], sizeof(cmd) - pos, " %s", node->arg[i]); } exec_apply_handler_script(cmd); list_del(&node->list); FREE(node); } if (monitor) { ULOG_INFO("Deferring request and setting up async completion"); async_req->idx = idx; ubus_defer_request(ctx, req, &async_req->req); async_req->timeout.cb = complete_request_callback; uloop_timeout_set(&async_req->timeout, 2000); } else { ULOG_INFO("Sending immediate success response"); send_reply(ctx, req, "status", "ok"); // Send 'bbf.apply' event send_bbf_apply_event(idx, changed_uci); // Free the allocated memory FREE(async_req->services); FREE(async_req); // Set internal commit to false g_internal_commit = false; ULOG_INFO("Commit handler exit"); } return 0; } static int bbf_config_revert_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg) { struct blob_attr *tb[__MAX]; unsigned char idx = 0; ULOG_INFO("Revert handler called"); if (blobmsg_parse(bbf_config_policy, __MAX, tb, blob_data(msg), blob_len(msg))) { send_reply(ctx, req, "error", "Failed to parse blob"); return -1; } if (tb[SERVICES_PROTO]) { char *proto = blobmsg_get_string(tb[SERVICES_PROTO]); idx = get_idx_by_proto(proto); ULOG_DEBUG("Protocol index determined as %d for protocol '%s'", idx, proto); } trigger_dfm_service_transactions(ctx, "abort", tb[SERVICES_PROTO] ? blobmsg_get_string(tb[SERVICES_PROTO]) : "both"); struct blob_attr *services = tb[SERVICES_NAME]; size_t arr_len = (services) ? blobmsg_len(services) : 0; if (arr_len) { ULOG_INFO("Reverting specified services"); reload_specified_services(ctx, idx, services, false, false, NULL, NULL, NULL); } else { ULOG_INFO("Reverting changes to dmmap UCI config"); uci_apply_changes_dmmap(idx, false, NULL, NULL); // revert dmmap changes ULOG_INFO("Reverting all services"); reload_all_services(ctx, idx, false, false, NULL, NULL, NULL); } ULOG_INFO("Sending success response"); send_reply(ctx, req, "status", "ok"); ULOG_INFO("revert handler exit"); return 0; } static void free_changed_uci_list(struct list_head *uci_list) { struct modi_uci_node *node = NULL, *tmp = NULL; if (uci_list == NULL) return; list_for_each_entry_safe(node, tmp, uci_list, list) { list_del(&node->list); FREE(node->uci); FREE(node); } } static void receive_notify_event(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *type, struct blob_attr *msg) { char file_path[1024] = {0}; struct blob_attr *tb[1] = {0}; const struct blobmsg_policy p[1] = { { "config", BLOBMSG_TYPE_STRING } }; blobmsg_parse(p, 1, tb, blob_data(msg), blob_len(msg)); if (!tb[0]) return; char *config = blobmsg_get_string(tb[0]); if (strlen(config) == 0) return; snprintf(file_path, sizeof(file_path), "/etc/config/%s", config); if (g_internal_commit) { ULOG_DEBUG("internal commit in progress, add uci in global list"); add_changed_uci_list(&g_external_changed_uci, file_path); return; } // Trigger 'bbfdm.apply' event struct list_head uci_list; INIT_LIST_HEAD(&uci_list); add_changed_uci_list(&uci_list, file_path); send_bbf_apply_event(-1, &uci_list); free_changed_uci_list(&uci_list); return; } static const struct ubus_method bbf_config_methods[] = { UBUS_METHOD("commit", bbf_config_commit_handler, bbf_config_policy), UBUS_METHOD("revert", bbf_config_revert_handler, bbf_config_policy), }; static struct ubus_object_type bbf_config_object_type = UBUS_OBJECT_TYPE("bbf.config", bbf_config_methods); static struct ubus_object bbf_config_object = { .name = "bbf.config", .type = &bbf_config_object_type, .methods = bbf_config_methods, .n_methods = ARRAY_SIZE(bbf_config_methods), }; static void usage(char *prog) { fprintf(stderr, "Usage: %s [options]\n", prog); fprintf(stderr, "\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " -l <0-4> Set the loglevel\n"); fprintf(stderr, " -h Displays this help\n"); fprintf(stderr, "\n"); } static void load_critical_services() { memset(&g_critical_bb, 0, sizeof(struct blob_buf)); blob_buf_init(&g_critical_bb, 0); blobmsg_add_json_from_file(&g_critical_bb, CRITICAL_DEF_JSON); } static int filter(const struct dirent *entry) { return entry->d_name[0] != '.'; } static int compare(const struct dirent **a, const struct dirent **b) { size_t len_a = strlen((*a)->d_name); size_t len_b = strlen((*b)->d_name); if (len_a < len_b) // Sort by length (shorter first) return -1; if (len_a > len_b) return 1; return strcasecmp((*a)->d_name, (*b)->d_name); // If lengths are equal, sort alphabetically } static void free_apply_handlers() { struct applier_node *node = NULL, *tmp = NULL; list_for_each_entry_safe(node, tmp, &g_apply_handlers, list) { list_del(&node->list); FREE(node->file_path); FREE(node->action); FREE(node); } } static void __load_handlers(const char *file) { if (file == NULL || strlen(file) == 0) return; json_object *json_root = json_object_from_file(file); if (!json_root) { ULOG_INFO("Failed to read json file %s", file); return; } json_object *daemon_config = NULL; json_object_object_get_ex(json_root, "daemon", &daemon_config); if (!daemon_config) { ULOG_INFO("Failed to find daemon object"); json_object_put(json_root); return; } json_object *apply_handler = NULL; json_object_object_get_ex(daemon_config, "apply_handler", &apply_handler); if (!apply_handler) { json_object_put(json_root); return; } char type[2][8] = { "dmmap", "uci" }; for (int i = 0; i < 2; i++) { json_object *array = NULL; if (!json_object_object_get_ex(apply_handler, type[i], &array) || json_object_get_type(array) != json_type_array) { continue; } size_t count = json_object_array_length(array); for (size_t j = 0; j < count; j++) { json_object *hndl_obj = json_object_array_get_idx(array, j); json_object *files = NULL, *handler = NULL; json_object_object_get_ex(hndl_obj, "external_handler", &handler); if (!handler) { continue; } const char *action = json_object_get_string(handler); if (strlen(action) == 0 || !file_exists(action)) { continue; } json_object_object_get_ex(hndl_obj, "file", &files); if (!files || json_object_get_type(files) != json_type_array) { continue; } size_t f_count = json_object_array_length(files); for (size_t k = 0; k < f_count; k++) { char path[1024] = {0}; json_object *f_inst = json_object_array_get_idx(files, k); snprintf(path, sizeof(path), "/etc/%s/%s", (strcmp(type[i], "uci") == 0) ? "config" : "bbfdm/dmmap", json_object_get_string(f_inst)); // check if already present bool exist = false; struct applier_node *node = NULL; list_for_each_entry(node, &g_apply_handlers, list) { if (strcmp(node->file_path, path) == 0 && strcmp(node->action, action) == 0) { exist = true; break; } } if (exist == true) continue; node = (struct applier_node *)calloc(1, sizeof(struct applier_node)); if (node == NULL) { ULOG_INFO("Failed to allocate memory for apply handlers"); json_object_put(json_root); return; } INIT_LIST_HEAD(&node->list); list_add_tail(&node->list, &g_apply_handlers); node->file_path = strdup(path); node->action = strdup(action); } } } json_object_put(json_root); return; } static void load_apply_handlers() { struct dirent **namelist; INIT_LIST_HEAD(&g_apply_handlers); int num_files = scandir(BBFDM_MICROSERVICE_INPUT_PATH, &namelist, filter, compare); for (int i = 0; i < num_files; i++) { char file_path[512] = {0}; snprintf(file_path, sizeof(file_path), "%s/%s", BBFDM_MICROSERVICE_INPUT_PATH, namelist[i]->d_name); if (!file_exists(file_path) || !regular_file(file_path)) { free(namelist[i]); continue; } __load_handlers(file_path); free(namelist[i]); } if (namelist) free(namelist); return; } int main(int argc, char **argv) { struct ubus_event_handler ev = { .cb = receive_notify_event, }; struct ubus_context *uctx; int ch, log_level = DEFAULT_LOG_LEVEL; while ((ch = getopt(argc, argv, "hl:")) != -1) { switch (ch) { case 'l': if (optarg) { log_level = (int)strtod(optarg, NULL); if (log_level < 0 || log_level > 7) { log_level = DEFAULT_LOG_LEVEL; } } break; case 'h': usage(argv[0]); exit(0); default: break; } } ulog_open(ULOG_SYSLOG, LOG_DAEMON, "bbf.config"); ulog_threshold(LOG_ERR + log_level); uctx = ubus_connect(NULL); if (uctx == NULL) { ULOG_ERR("Failed to create UBUS context"); return -1; } uloop_init(); ubus_add_uloop(uctx); load_critical_services(); load_apply_handlers(); INIT_LIST_HEAD(&g_external_changed_uci); if (ubus_add_object(uctx, &bbf_config_object)) { ULOG_ERR("Failed to add 'bbf.config' ubus object"); goto exit; } if (ubus_register_event_handler(uctx, &ev, "bbf.config.notify")) { ULOG_ERR("Failed to register 'bbf.config.notify' event handler"); goto exit; } uloop_run(); exit: free_apply_handlers(); free_changed_uci_list(&g_external_changed_uci); blob_buf_free(&g_critical_bb); uloop_done(); ubus_free(uctx); ulog_close(); return 0; }