/* * Copyright (C) 2024 iopsys Software Solutions AB * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation * * Author: Amin Ben Romdhane * */ #include "utils.h" #include "fwbank.h" #ifdef SYSMNGR_FWBANK_UBUS_SUPPORT const char fw_image_dependency[] = "file:/etc/sysmngr/fwbank"; #define FWBANK_FILE_PATH "/etc/sysmngr/fwbank" #else const char fw_image_dependency[] = "file:/usr/libexec/rpcd/fwbank"; #define FWBANK_FILE_PATH "/usr/libexec/rpcd/fwbank" #endif #define FWBANK_DUMP_CMD FWBANK_FILE_PATH " call dump" #define FWBANK_SET_BOOTBANK_CMD FWBANK_FILE_PATH " call set_bootbank" #define FWBANK_UPGRADE_CMD FWBANK_FILE_PATH " call upgrade" #define FWBANK_DUMP_MAX_RETRIES 10 #define FWBANK_DUMP_RETRY_DELAY 3 struct fwbank_event_data { struct ubus_context *ctx; struct uloop_timeout tm; struct ubus_event_handler ev; uint32_t band_id; int timeout; }; struct fwbank_dump_data { struct ubus_context *ctx; struct uloop_timeout tm; struct blob_buf output; }; static struct fwbank_dump_data g_fwbank_dump = {0}; static int g_retry_count = 0; typedef void (*sysmngr_task_callback_t)(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id); typedef struct sysmngr_task_data { struct ubus_context *ctx; struct ubus_request_data *req; struct ubus_request_data new_req; struct uloop_process process; // Used for forked task struct uloop_timeout timeoutcb; // Timeout for the task sysmngr_task_callback_t finishcb; // Finish callback for parent process const char *command; // Command to execute uint32_t bank_id; int pipe_fds[2]; } sysmngr_task_data_t; struct blobmsg_policy sysmngr_dump_policy[] = { { "bank", BLOBMSG_TYPE_ARRAY } }; struct blobmsg_policy sysmngr_bank_policy[] = { { "name", BLOBMSG_TYPE_STRING }, { "id", BLOBMSG_TYPE_INT32 }, { "active", BLOBMSG_TYPE_BOOL }, { "boot", BLOBMSG_TYPE_BOOL }, { "upgrade", BLOBMSG_TYPE_BOOL }, { "fwver", BLOBMSG_TYPE_STRING }, { "swver", BLOBMSG_TYPE_STRING }, { "status", BLOBMSG_TYPE_STRING } }; static void _sysmngr_task_finish_callback(struct uloop_process *p, int ret) { struct sysmngr_task_data *task = NULL; task = container_of(p, struct sysmngr_task_data, process); if (task == NULL) { BBFDM_ERR("Failed to decode forked task"); return; } if (task->timeoutcb.pending) { // Cancel timeout if the task finishes on its own uloop_timeout_cancel(&task->timeoutcb); } if (task->finishcb) { task->finishcb(task->ctx, &task->new_req, task->pipe_fds, task->bank_id); } if (task->ctx && task->req) { ubus_complete_deferred_request(task->ctx, &task->new_req, 0); } FREE(task); } static void task_callback(const char *command, int *pipe_fds) { // Redirect stdout to the pipe's write end dup2(pipe_fds[1], STDOUT_FILENO); close(pipe_fds[0]); // Close unused read end close(pipe_fds[1]); // Close write end after dup2 if (!command) exit(EXIT_FAILURE); char *line = NULL; ssize_t read; size_t len; FILE *pipe = popen(command, "r"); // flawfinder: ignore if (pipe == NULL) exit(EXIT_FAILURE); while ((read = getline(&line, &len, pipe)) != -1) { // Output the command's result to stdout line[read - 1] = '\0'; // Remove trailing newline printf("%s", line); } free(line); if (pclose(pipe) == -1) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } static void timeout_callback(struct uloop_timeout *t) { sysmngr_task_data_t *task = container_of(t, sysmngr_task_data_t, timeoutcb); if (task && task->process.pid > 0) { BBFDM_ERR("Task timed out. Killing process with PID %d\n", task->process.pid); kill(task->process.pid, SIGKILL); } } static int sysmngr_task_fork(sysmngr_task_callback_t finishcb, const char *command, int timeout, struct ubus_request_data *req, uint32_t bank_id) { sysmngr_task_data_t *task; pid_t child; task = (sysmngr_task_data_t *)calloc(sizeof(sysmngr_task_data_t), 1); if (task == NULL) { return -1; } if (pipe(task->pipe_fds) == -1) { BBFDM_ERR("pipe failed"); FREE(task); return -1; } task->command = command; task->ctx = g_fwbank_dump.ctx; task->req = req; task->bank_id = bank_id; child = fork(); if (child == -1) { BBFDM_ERR("Failed to fork a child for task"); FREE(task); return -1; } else if (child == 0) { /* free fd's and memory inherited from parent */ uloop_done(); fclose(stdin); fclose(stderr); task_callback(task->command, task->pipe_fds); /* write result and exit */ exit(EXIT_SUCCESS); } if (finishcb) { task->finishcb = finishcb; task->process.pid = child; task->process.cb = _sysmngr_task_finish_callback; uloop_process_add(&task->process); } if (task->ctx && task->req) { ubus_defer_request(task->ctx, task->req, &task->new_req); } if (timeout > 0) { task->timeoutcb.cb = timeout_callback; uloop_timeout_set(&task->timeoutcb, 1000 * timeout); } return 0; } static void fwbank_dump_timer(struct uloop_timeout *timeout) { struct fwbank_dump_data *data = NULL; BBFDM_DEBUG("fwbank_dump_timer triggered"); data = container_of(timeout, struct fwbank_dump_data, tm); if (data == NULL) return; sysmngr_init_fwbank_dump(data->ctx); } static int free_global_fwbank_dump(struct blob_buf *fwbank_dump_bb) { if (fwbank_dump_bb->head && blob_len(fwbank_dump_bb->head)) { BBFDM_DEBUG("Freeing fwbank dump blob buffer"); blob_buf_free(fwbank_dump_bb); } else { BBFDM_DEBUG("fwbank dump blob buffer is already empty"); } return 0; } static int validate_global_fwbank_dump(struct blob_buf *fwbank_dump_bb) { if (!fwbank_dump_bb->head || !blob_len(fwbank_dump_bb->head)) { BBFDM_ERR("fwbank dump output is empty"); return -1; } BBFDM_DEBUG("Validating global fwbank dump"); struct blob_attr *tb[1] = {0}; if (blobmsg_parse(sysmngr_dump_policy, 1, tb, blobmsg_data(fwbank_dump_bb->head), blobmsg_len(fwbank_dump_bb->head))) { BBFDM_ERR("Failed to parse fwbank dump blob"); return -1; } if (!tb[0]) { // bank array is not found BBFDM_ERR("Bank array not found in fwbank dump"); return -1; } struct blob_attr *entry = NULL; int rem = 0, valid = -1; blobmsg_for_each_attr(entry, tb[0], rem) { // parse bank array struct blob_attr *t[8] = {0}; if (blobmsg_parse(sysmngr_bank_policy, ARRAY_SIZE(sysmngr_bank_policy), t, blobmsg_data(entry), blobmsg_len(entry))) { BBFDM_ERR("Failed to parse bank entry"); continue; } // mark the valid flag, if one bank has valid data if (t[0] && t[1] && t[2] && t[3] && t[4] && t[5] && t[6] && t[7]) { valid = 0; break; } } BBFDM_DEBUG("Global fwbank dump validation passed"); return valid; } static void fwbank_dump_finish_callback(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id) { BBFDM_DEBUG("Task finished Line=%d && func=%s", __LINE__, __func__); close(pipe_fds[1]); // Close unused write end char buffer[1024] = {0}; ssize_t bytes_read; BBFDM_DEBUG("Reading script output..."); // Read the output from the script while ((bytes_read = read(pipe_fds[0], buffer, sizeof(buffer) - 1)) > 0) { buffer[bytes_read] = '\0'; // Null-terminate the buffer BBFDM_DEBUG("Script output: %s", buffer); } close(pipe_fds[0]); // Close read end if (bytes_read < 0 || strlen(buffer) == 0) { BBFDM_ERR("Failed to read from pipe"); goto retry; } // Use a temporary blob_buf for validation struct blob_buf temp_buf = {0}; memset(&temp_buf, 0, sizeof(struct blob_buf)); blob_buf_init(&temp_buf, 0); if (!blobmsg_add_json_from_string(&temp_buf, buffer)) { BBFDM_ERR("Invalid JSON format in buffer"); blob_buf_free(&temp_buf); goto retry; } int res = validate_global_fwbank_dump(&temp_buf); if (res) { BBFDM_ERR("Failed to validate 'fwbank' output"); blob_buf_free(&temp_buf); goto retry; } free_global_fwbank_dump(&g_fwbank_dump.output); // Init g_fwbank_dump.output memset(&g_fwbank_dump.output, 0, sizeof(struct blob_buf)); blob_buf_init(&g_fwbank_dump.output, 0); // Move validated JSON from temp_buf to g_fwbank_dump.output blobmsg_add_blob(&g_fwbank_dump.output, blob_data(temp_buf.head)); // Free the temporary buffer blob_buf_free(&temp_buf); return; retry: if (g_retry_count < FWBANK_DUMP_MAX_RETRIES) { g_retry_count++; uloop_timeout_set(&g_fwbank_dump.tm, FWBANK_DUMP_RETRY_DELAY * 1000); BBFDM_ERR("Attempt %d/%d: fwbank dump blob buf is empty. Retrying in %d second(s)...", g_retry_count, FWBANK_DUMP_MAX_RETRIES, FWBANK_DUMP_RETRY_DELAY); } else { BBFDM_ERR("Max retries (%d) reached: The fwbank dump buffer is empty. Unable to register 'fwbank' ubus object", FWBANK_DUMP_MAX_RETRIES); } } static int init_global_fwbank_dump(void) { BBFDM_DEBUG("Initializing global fwbank dump"); int res = sysmngr_task_fork(fwbank_dump_finish_callback, FWBANK_DUMP_CMD, 120, NULL, 0); if (res) { BBFDM_ERR("Failed to start task for fwbank dump command"); return -1; } BBFDM_DEBUG("fwbank dump blob initialized successfully"); return 0; } static void fwbank_listen_timeout(struct uloop_timeout *timeout) { struct fwbank_event_data *data = NULL; BBFDM_DEBUG("fwbank listen timeout triggered"); data = container_of(timeout, struct fwbank_event_data, tm); if (data == NULL) return; ubus_unregister_event_handler(data->ctx, &data->ev); } static void fwbank_receive_sysupgrade(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *type, struct blob_attr *msg) { struct fwbank_event_data *data = NULL; struct blob_attr *cur = NULL; char bank_id_str[16] = {0}; int rem; if (!msg || !ev) { BBFDM_ERR("Invalid event data in sysupgrade handler"); return; } data = container_of(ev, struct fwbank_event_data, ev); if (data == NULL) return; if (data->band_id < 0) { // bank_id should be a valid id BBFDM_ERR("Invalid bank_id: %d", data->band_id); return; } snprintf(bank_id_str, sizeof(bank_id_str), "%u", data->band_id); blobmsg_for_each_attr(cur, msg, rem) { if (DM_STRCMP("bank_id", blobmsg_name(cur)) == 0) { char *attr_val = (char *)blobmsg_data(cur); if (DM_STRCMP(attr_val, bank_id_str) != 0) { BBFDM_ERR("Mismatched bank_id (%s != %s)", attr_val, bank_id_str); return; } } if (DM_STRCMP("status", blobmsg_name(cur)) == 0) { char *attr_val = (char *)blobmsg_data(cur); if (DM_STRCMP(attr_val, "Downloading") == 0) { BBFDM_DEBUG("Sysupgrade status: Downloading"); return; } if (DM_STRCMP(attr_val, "Available") == 0) { BBFDM_DEBUG("Sysupgrade status: Available. Refreshing fwbank dump."); init_global_fwbank_dump(); break; } } } if (data->tm.pending) { // Cancel timeout if the sysupgrade status available is found uloop_timeout_cancel(&data->tm); } ubus_unregister_event_handler(data->ctx, &data->ev); return; } static struct fwbank_event_data g_fwbank_event_data = { .tm.cb = fwbank_listen_timeout, .ev.cb = fwbank_receive_sysupgrade, .timeout = 120 }; static void fwbank_wait_for_sysupgrade_event(struct ubus_context *ctx, uint32_t band_id) { BBFDM_DEBUG("Waiting for sysupgrade event for bank_id: %u", band_id); g_fwbank_event_data.ctx = ctx; g_fwbank_event_data.band_id = band_id; ubus_register_event_handler(ctx, &g_fwbank_event_data.ev, "sysupgrade"); uloop_timeout_set(&g_fwbank_event_data.tm, g_fwbank_event_data.timeout * 1000); } static bool is_set_bootbank_success(struct blob_buf *output_bb) { struct blob_attr *tb = NULL; struct blobmsg_policy policy = { .name = "success", .type = BLOBMSG_TYPE_BOOL, }; // Parse the blob buffer for the "success" field if (blobmsg_parse(&policy, 1, &tb, blobmsg_data(output_bb->head), blobmsg_len(output_bb->head)) != 0) { BBFDM_ERR("Failed to parse blobmsg data"); return false; } // Check if the "success" field exists and is of the correct type if (tb && blobmsg_type(tb) == BLOBMSG_TYPE_BOOL) return blobmsg_get_bool(tb); return false; } static bool is_upgrade_success(struct blob_buf *output_bb) { struct blob_attr *tb = NULL; struct blobmsg_policy policy = { .name = "result", .type = BLOBMSG_TYPE_STRING, }; // Parse the blob buffer for the "result" field if (blobmsg_parse(&policy, 1, &tb, blobmsg_data(output_bb->head), blobmsg_len(output_bb->head)) != 0) { BBFDM_ERR("Failed to parse blobmsg data"); return false; } // Check if the "result" field exists and is of the correct type if (tb && blobmsg_type(tb) == BLOBMSG_TYPE_STRING) return (strcmp(blobmsg_get_string(tb), "ok") == 0) ? true : false; return false; } struct blob_buf *sysmngr_fwbank_dump(void) { return &g_fwbank_dump.output; } static void fwbank_set_bootbank_finish_callback(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id) { BBFDM_DEBUG("Task finished Line=%d && func=%s", __LINE__, __func__); close(pipe_fds[1]); // Close unused write end char buffer[1024] = {0}; ssize_t bytes_read; BBFDM_DEBUG("Reading script output..."); // Read the output from the script while ((bytes_read = read(pipe_fds[0], buffer, sizeof(buffer) - 1)) > 0) { buffer[bytes_read] = '\0'; // Null-terminate the buffer BBFDM_DEBUG("Script output: %s", buffer); } close(pipe_fds[0]); // Close read end if (bytes_read < 0) { BBFDM_ERR("Failed to read from pipe"); return; } struct blob_buf setbootbank_bb = {0}; memset(&setbootbank_bb, 0, sizeof(struct blob_buf)); blob_buf_init(&setbootbank_bb, 0); if (!blobmsg_add_json_from_string(&setbootbank_bb, buffer)) { BBFDM_ERR("Failed to create blob buf"); blob_buf_free(&setbootbank_bb); return; } bool is_success = is_set_bootbank_success(&setbootbank_bb); if (ctx && req) { BBFDM_DEBUG("Send ubus output"); ubus_send_reply(ctx, req, setbootbank_bb.head); } blob_buf_free(&setbootbank_bb); if (is_success) { // Update fwbank dump output init_global_fwbank_dump(); } return; } int sysmngr_fwbank_set_bootbank(uint32_t bank_id, struct ubus_request_data *req) { char cmd[128] = {0}; snprintf(cmd, sizeof(cmd), "echo '{\"bank\":%u}' | %s 2>/dev/null", bank_id, FWBANK_SET_BOOTBANK_CMD); int res = sysmngr_task_fork(fwbank_set_bootbank_finish_callback, cmd, 10, req, 0); if (res) { BBFDM_ERR("Failed to start task for fwbank set bootbank command"); return -1; } return 0; } static void fwbank_upgrade_finish_callback(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id) { BBFDM_DEBUG("Task finished Line=%d && func=%s", __LINE__, __func__); close(pipe_fds[1]); // Close unused write end char buffer[1024] = {0}; ssize_t bytes_read; BBFDM_DEBUG("Reading script output..."); // Read the output from the script while ((bytes_read = read(pipe_fds[0], buffer, sizeof(buffer) - 1)) > 0) { buffer[bytes_read] = '\0'; // Null-terminate the buffer BBFDM_DEBUG("Script output: %s", buffer); } close(pipe_fds[0]); // Close read end if (bytes_read < 0) { BBFDM_ERR("Failed to read from pipe"); return; } struct blob_buf upgrade_bb = {0}; memset(&upgrade_bb, 0, sizeof(struct blob_buf)); blob_buf_init(&upgrade_bb, 0); if (!blobmsg_add_json_from_string(&upgrade_bb, buffer)) { BBFDM_ERR("Failed to create blob buf"); blob_buf_free(&upgrade_bb); return; } bool is_success = is_upgrade_success(&upgrade_bb); if (ctx && req) { BBFDM_DEBUG("Send ubus output"); ubus_send_reply(ctx, req, upgrade_bb.head); } blob_buf_free(&upgrade_bb); if (ctx && is_success) { // Update fwbank dump output after getting sysupgrade event with status available // { "sysupgrade": {"status":"Available","bank_id":"x"} } fwbank_wait_for_sysupgrade_event(ctx, bank_id); } return; } int sysmngr_fwbank_upgrade(const char *path, bool auto_activate, uint32_t bank_id, bool keep_settings, struct ubus_request_data *req) { char cmd[1024] = {0}; snprintf(cmd, sizeof(cmd), "echo '{\"path\":\"%s\", \"auto_activate\":%d, \"bank\":%u, \"keep_settings\":%d}' | %s 2>/dev/null", path, auto_activate, bank_id, keep_settings, FWBANK_UPGRADE_CMD); int res = sysmngr_task_fork(fwbank_upgrade_finish_callback, cmd, 10, req, bank_id); if (res) { BBFDM_ERR("Failed to start task for fwbank upgrade command"); return -1; } return 0; } static int dump_handler(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { ubus_send_reply(ctx, req, g_fwbank_dump.output.head); return 0; } enum { SET_BOOT_BANK, __SET_BOOT_MAX }; static const struct blobmsg_policy set_bootbank_policy[] = { [SET_BOOT_BANK] = { .name = "bank", .type = BLOBMSG_TYPE_INT32 }, }; static int set_bootbank_handler(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__SET_BOOT_MAX]; uint32_t band_id = 0; int res = 0; if (blobmsg_parse(set_bootbank_policy, __SET_BOOT_MAX, tb, blob_data(msg), blob_len(msg))) { BBFDM_ERR("Failed to parse the 'set_bootbank' message"); return UBUS_STATUS_UNKNOWN_ERROR; } if (tb[SET_BOOT_BANK]) band_id = blobmsg_get_u32(tb[SET_BOOT_BANK]); res = sysmngr_fwbank_set_bootbank(band_id, req); if (res) { struct blob_buf bb = {0}; memset(&bb, 0, sizeof(struct blob_buf)); blob_buf_init(&bb, 0); blobmsg_add_u8(&bb, "status", false); ubus_send_reply(ctx, req, bb.head); blob_buf_free(&bb); } return 0; } enum { UPGRADE_PATH, UPGRADE_AUTO_ACTIVATE, UPGRADE_BANK, UPGRADE_KEEP_SETTINGS, __UPGRADE_MAX }; static const struct blobmsg_policy upgrade_policy[] = { [UPGRADE_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, [UPGRADE_AUTO_ACTIVATE] = { .name = "auto_activate", .type = BLOBMSG_TYPE_BOOL }, [UPGRADE_BANK] = { .name = "bank", .type = BLOBMSG_TYPE_INT32 }, [UPGRADE_KEEP_SETTINGS] = { .name = "keep_settings", .type = BLOBMSG_TYPE_BOOL}, }; static int upgrade_handler(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__UPGRADE_MAX]; char *fw_path = NULL; uint32_t bank_id = 0; bool auto_activate = false; bool keep_settings = false; int res = 0; if (blobmsg_parse(upgrade_policy, __UPGRADE_MAX, tb, blob_data(msg), blob_len(msg))) { BBFDM_ERR("Failed to parse the 'upgrade' message"); return UBUS_STATUS_UNKNOWN_ERROR; } if (tb[UPGRADE_PATH]) fw_path = blobmsg_get_string(tb[UPGRADE_PATH]); if (tb[UPGRADE_AUTO_ACTIVATE]) auto_activate = blobmsg_get_bool(tb[UPGRADE_AUTO_ACTIVATE]); if (tb[UPGRADE_BANK]) bank_id = blobmsg_get_u32(tb[UPGRADE_BANK]); if (tb[UPGRADE_KEEP_SETTINGS]) { keep_settings = blobmsg_get_bool(tb[UPGRADE_KEEP_SETTINGS]); } else { char buf[8] = {0}; BBFDM_UCI_GET("sysmngr", "globals", "keep_config", "1", buf, sizeof(buf)); keep_settings = ((int)strtol(buf, NULL, 10) != 0); BBFDM_DEBUG("'keep_settings' option is not provided. Falling back to the value from UCI config: 'sysmngr.globals.keep_config'"); } res = sysmngr_fwbank_upgrade(fw_path, auto_activate, bank_id, keep_settings, req); if (res) { struct blob_buf bb = {0}; memset(&bb, 0, sizeof(struct blob_buf)); blob_buf_init(&bb, 0); blobmsg_add_string(&bb, "result", "failure"); ubus_send_reply(ctx, req, bb.head); blob_buf_free(&bb); } return 0; } static struct ubus_method fwbank_methods[] = { UBUS_METHOD_NOARG("dump", dump_handler), UBUS_METHOD("set_bootbank", set_bootbank_handler, set_bootbank_policy), UBUS_METHOD("upgrade", upgrade_handler, upgrade_policy), }; static struct ubus_object_type fwbank_type = UBUS_OBJECT_TYPE("fwbank", fwbank_methods); static struct ubus_object fwbank_object = { .name = "fwbank", .type = &fwbank_type, .methods = fwbank_methods, .n_methods = ARRAY_SIZE(fwbank_methods) }; int sysmngr_register_fwbank(struct ubus_context *ubus_ctx) { int res = ubus_add_object(ubus_ctx, &fwbank_object); if (res) { BBFDM_ERR("Failed to register 'fwbank' ubus object!!!!!!"); return -1; } BBFDM_INFO("'fwbank' ubus object was registered"); return res; } int sysmngr_unregister_fwbank(struct ubus_context *ubus_ctx) { ubus_remove_object(ubus_ctx, &fwbank_object); BBFDM_INFO("'fwbank' ubus object was unregistered, and resources were freed"); return 0; } int sysmngr_init_fwbank_dump(struct ubus_context *ubus_ctx) { int res = 0; g_fwbank_dump.ctx = ubus_ctx; g_fwbank_dump.tm.cb = fwbank_dump_timer; if (!file_exists(FWBANK_FILE_PATH)) { BBFDM_ERR("The fwbank file (%s) is missing", FWBANK_FILE_PATH); return -1; } res = init_global_fwbank_dump(); if (res) { BBFDM_ERR("Failed to fetch 'fwbank' output or no data available"); return -1; } return 0; } int sysmngr_clean_fwbank_dump(struct ubus_context *ubus_ctx) { free_global_fwbank_dump(&g_fwbank_dump.output); return 0; }