// SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest #include "systemImpl.hpp" #include #include #include #include #include #include #include #include using namespace everest::run_application; namespace module { namespace main { const std::string CONSTANTS = "constants.env"; const std::string DIAGNOSTICS_UPLOADER = "diagnostics_uploader.sh"; const std::string FIRMWARE_UPDATER = "firmware_updater.sh"; const std::string SIGNED_FIRMWARE_DOWNLOADER = "signed_firmware_downloader.sh"; const std::string SIGNED_FIRMWARE_INSTALLER = "signed_firmware_installer.sh"; namespace fs = std::filesystem; // FIXME (aw): this function needs to be refactored into some kind of utility library fs::path create_temp_file(const fs::path& dir, const std::string& prefix) { const std::string fn_template = (dir / prefix).string() + "XXXXXX" + std::string(1, '\0'); std::vector fn_template_buffer{fn_template.begin(), fn_template.end()}; // mkstemp needs to have at least 6 XXXXXX at the end and it will replace these // with a valid file name auto fd = mkstemp(fn_template_buffer.data()); if (fd == -1) { EVLOG_error << "Failed to create temporary file at: " << fn_template; return {}; } // close the file descriptor close(fd); return fn_template_buffer.data(); } void systemImpl::init() { this->scripts_path = mod->info.paths.libexec; this->log_upload_running = false; this->firmware_download_running = false; this->firmware_installation_running = false; this->standard_firmware_update_running = false; this->boot_reason_key = "ocpp_boot_reason"; } void systemImpl::ready() { } void systemImpl::standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) { this->standard_firmware_update_running = true; EVLOG_info << "Starting firmware update"; // create temporary file const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now()); const auto firmware_file_path = create_temp_file(fs::temp_directory_path(), "firmware-" + date_time); if (firmware_file_path.empty()) { EVLOG_error << "Firmware update ignored, cannot write temporary file."; publish_firmware_update_status({types::system::FirmwareUpdateStatusEnum::DownloadFailed}); return; } const auto constants = this->scripts_path / CONSTANTS; this->update_firmware_thread = std::thread([this, firmware_update_request, firmware_file_path, constants]() { const auto firmware_updater = this->scripts_path / FIRMWARE_UPDATER; const std::vector args = {constants.string(), firmware_update_request.location, firmware_file_path.string()}; int32_t retries = 0; const auto total_retries = firmware_update_request.retries.value_or(this->mod->config.DefaultRetries); const auto retry_interval = firmware_update_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval); auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::DownloadFailed; types::system::FirmwareUpdateStatus firmware_status; firmware_status.request_id = -1; firmware_status.firmware_update_status = firmware_status_enum; while (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed && retries < total_retries) { retries += 1; run_application(firmware_updater.string(), args, [this, &firmware_status](const std::string& output_line) { firmware_status.firmware_update_status = types::system::string_to_firmware_update_status_enum(output_line); this->publish_firmware_update_status(firmware_status); return CmdControl::Continue; }); if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed && retries < total_retries) { std::this_thread::sleep_for(std::chrono::seconds(retry_interval)); } if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::Installed and !this->mod->r_store.empty()) { this->mod->r_store.at(0)->call_store(boot_reason_key, boot_reason_to_string(types::system::BootReason::FirmwareUpdate)); } } this->standard_firmware_update_running = false; }); this->update_firmware_thread.detach(); } types::system::UpdateFirmwareResponse systemImpl::handle_standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) { if (!this->standard_firmware_update_running) { if (firmware_update_request.retrieve_timestamp.has_value() && Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()) > date::utc_clock::now()) { const auto retrieve_timestamp = Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()); this->standard_update_firmware_timer.at( [this, retrieve_timestamp, firmware_update_request]() { this->standard_firmware_update(firmware_update_request); }, retrieve_timestamp); EVLOG_info << "Download for firmware scheduled for: " << Everest::Date::to_rfc3339(retrieve_timestamp); } else { // start download immediately this->update_firmware_thread = std::thread( [this, firmware_update_request]() { this->standard_firmware_update(firmware_update_request); }); this->update_firmware_thread.detach(); } return types::system::UpdateFirmwareResponse::Accepted; } else { EVLOG_info << "Not starting firmware update because firmware update process already running"; return types::system::UpdateFirmwareResponse::Rejected; } } types::system::UpdateFirmwareResponse systemImpl::handle_signed_fimware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) { if (!firmware_update_request.signing_certificate.has_value()) { EVLOG_warning << "Signing certificate is missing in FirmwareUpdateRequest"; return types::system::UpdateFirmwareResponse::Rejected; } if (!firmware_update_request.signature.has_value()) { EVLOG_warning << "Signature is missing in FirmwareUpdateRequest"; return types::system::UpdateFirmwareResponse::Rejected; } EVLOG_info << "Executing signed firmware update download callback"; if (firmware_update_request.retrieve_timestamp.has_value() && Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()) > date::utc_clock::now()) { const auto retrieve_timestamp = Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()); this->signed_firmware_update_download_timer.at( [this, retrieve_timestamp, firmware_update_request]() { this->download_signed_firmware(firmware_update_request); }, retrieve_timestamp); EVLOG_info << "Download for firmware scheduled for: " << Everest::Date::to_rfc3339(retrieve_timestamp); types::system::FirmwareUpdateStatus firmware_update_status; firmware_update_status.request_id = firmware_update_request.request_id; firmware_update_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::DownloadScheduled; this->publish_firmware_update_status(firmware_update_status); } else { // start download immediately this->update_firmware_thread = std::thread([this, firmware_update_request]() { this->download_signed_firmware(firmware_update_request); }); this->update_firmware_thread.detach(); } if (this->firmware_download_running) { return types::system::UpdateFirmwareResponse::AcceptedCanceled; } else if (this->firmware_installation_running) { return types::system::UpdateFirmwareResponse::Rejected; } else { return types::system::UpdateFirmwareResponse::Accepted; } } void systemImpl::download_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request) { if (!firmware_update_request.signing_certificate.has_value()) { EVLOG_warning << "Signing certificate is missing in FirmwareUpdateRequest"; this->publish_firmware_update_status( {types::system::FirmwareUpdateStatusEnum::DownloadFailed, firmware_update_request.request_id}); return; } if (!firmware_update_request.signature.has_value()) { EVLOG_warning << "Signature is missing in FirmwareUpdateRequest"; this->publish_firmware_update_status( {types::system::FirmwareUpdateStatusEnum::DownloadFailed, firmware_update_request.request_id}); return; } if (this->firmware_download_running) { EVLOG_info << "Received Firmware update request and firmware update already running - cancelling firmware update"; this->interrupt_firmware_download.exchange(true); EVLOG_info << "Waiting for other firmware download to finish..."; std::unique_lock lk(this->firmware_update_mutex); this->firmware_update_cv.wait(lk, [this]() { return !this->firmware_download_running; }); EVLOG_info << "Previous Firmware download finished!"; } std::lock_guard lg(this->firmware_update_mutex); EVLOG_info << "Starting Firmware update"; this->interrupt_firmware_download.exchange(false); this->firmware_download_running = true; // // create temporary file const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now()); const auto firmware_file_path = create_temp_file(fs::temp_directory_path(), "signed_firmware-" + date_time); const auto firmware_downloader = this->scripts_path / SIGNED_FIRMWARE_DOWNLOADER; const auto constants = this->scripts_path / CONSTANTS; const std::vector download_args = { constants.string(), firmware_update_request.location, firmware_file_path.string(), firmware_update_request.signature.value(), firmware_update_request.signing_certificate.value()}; int32_t retries = 0; const auto total_retries = firmware_update_request.retries.value_or(this->mod->config.DefaultRetries); const auto retry_interval = firmware_update_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval); auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::DownloadFailed; types::system::FirmwareUpdateStatus firmware_status; firmware_status.request_id = firmware_update_request.request_id; firmware_status.firmware_update_status = firmware_status_enum; while (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed && retries < total_retries && !this->interrupt_firmware_download) { run_application( firmware_downloader.string(), download_args, [this, &firmware_status](const std::string& output_line) { firmware_status.firmware_update_status = types::system::string_to_firmware_update_status_enum(output_line); this->publish_firmware_update_status(firmware_status); if (this->interrupt_firmware_download) { EVLOG_info << "Updating firmware was interrupted, terminating firmware update script, requestId: " << firmware_status.request_id; return CmdControl::Terminate; } return CmdControl::Continue; }); retries += 1; if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed && retries < total_retries) { std::this_thread::sleep_for(std::chrono::seconds(retry_interval)); } } if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::SignatureVerified) { this->initialize_firmware_installation(firmware_update_request, firmware_file_path); } this->firmware_download_running = false; this->firmware_update_cv.notify_one(); EVLOG_info << "Firmware update thread finished"; } void systemImpl::initialize_firmware_installation(const types::system::FirmwareUpdateRequest& firmware_update_request, const fs::path& firmware_file_path) { if (firmware_update_request.install_timestamp.has_value() && Everest::Date::from_rfc3339(firmware_update_request.install_timestamp.value()) > date::utc_clock::now()) { const auto install_timestamp = Everest::Date::from_rfc3339(firmware_update_request.install_timestamp.value()); this->signed_firmware_update_install_timer.at( [this, firmware_update_request, firmware_file_path]() { this->install_signed_firmware(firmware_update_request, firmware_file_path); }, install_timestamp); EVLOG_info << "Installation for firmware scheduled for: " << Everest::Date::to_rfc3339(install_timestamp); types::system::FirmwareUpdateStatus firmware_update_status; firmware_update_status.request_id = firmware_update_request.request_id; firmware_update_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::InstallScheduled; this->publish_firmware_update_status(firmware_update_status); } else { // start installation immediately this->update_firmware_thread = std::thread([this, firmware_update_request, firmware_file_path]() { this->install_signed_firmware(firmware_update_request, firmware_file_path); }); this->update_firmware_thread.detach(); } } void systemImpl::install_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request, const fs::path& firmware_file_path) { auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::Installing; types::system::FirmwareUpdateStatus firmware_status; firmware_status.request_id = firmware_update_request.request_id; firmware_status.firmware_update_status = firmware_status_enum; if (!this->firmware_installation_running) { this->firmware_installation_running = true; const auto firmware_installer = this->scripts_path / SIGNED_FIRMWARE_INSTALLER; const auto constants = this->scripts_path / CONSTANTS; const std::vector install_args = {constants.string()}; run_application(firmware_installer.string(), install_args, [this, &firmware_status](const std::string& output_line) { firmware_status.firmware_update_status = types::system::string_to_firmware_update_status_enum(output_line); this->publish_firmware_update_status(firmware_status); return CmdControl::Continue; }); if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::Installed) { if (!this->mod->r_store.empty()) { this->mod->r_store.at(0)->call_store(boot_reason_key, boot_reason_to_string(types::system::BootReason::FirmwareUpdate)); } auto reset_type = types::system::ResetType::Hard; bool firmware_installation_running_copy = this->firmware_installation_running; this->handle_reset(reset_type, firmware_installation_running_copy); } } else { firmware_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::InstallationFailed; this->publish_firmware_update_status(firmware_status); } } types::system::UpdateFirmwareResponse systemImpl::handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) { if (firmware_update_request.request_id == -1) { return this->handle_standard_firmware_update(firmware_update_request); } else { return this->handle_signed_fimware_update(firmware_update_request); } }; void systemImpl::handle_allow_firmware_installation() { // TODO: implement me } types::system::UploadLogsResponse systemImpl::handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) { types::system::UploadLogsResponse response; if (this->log_upload_running) { response.upload_logs_status = types::system::UploadLogsStatus::AcceptedCanceled; } else { response.upload_logs_status = types::system::UploadLogsStatus::Accepted; } const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now()); // TODO(piet): consider start time and end time const auto diagnostics_file_path = create_temp_file(fs::temp_directory_path(), "diagnostics-" + date_time); const auto diagnostics_file_name = diagnostics_file_path.filename().string(); response.file_name = diagnostics_file_name; const auto fake_diagnostics_file = json({{"diagnostics", {{"key", "value"}}}}); std::ofstream diagnostics_file(diagnostics_file_path.c_str()); diagnostics_file << fake_diagnostics_file.dump(); this->upload_logs_thread = std::thread([this, upload_logs_request, diagnostics_file_name, diagnostics_file_path]() { if (this->log_upload_running) { EVLOG_info << "Received Log upload request and log upload already running - cancelling current upload"; this->interrupt_log_upload.exchange(true); EVLOG_info << "Waiting for other log upload to finish..."; std::unique_lock lk(this->log_upload_mutex); this->log_upload_cv.wait(lk, [this]() { return !this->log_upload_running; }); EVLOG_info << "Previous Log upload finished!"; } std::lock_guard lg(this->log_upload_mutex); EVLOG_info << "Starting upload of log file"; this->interrupt_log_upload.exchange(false); this->log_upload_running = true; const auto diagnostics_uploader = this->scripts_path / DIAGNOSTICS_UPLOADER; const auto constants = this->scripts_path / CONSTANTS; std::vector args = {constants.string(), upload_logs_request.location, diagnostics_file_name, diagnostics_file_path.string()}; bool uploaded = false; int32_t retries = 0; const auto total_retries = upload_logs_request.retries.value_or(this->mod->config.DefaultRetries); const auto retry_interval = upload_logs_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval); types::system::LogStatus log_status; while (!uploaded && retries < total_retries && !this->interrupt_log_upload) { retries += 1; log_status.request_id = upload_logs_request.request_id.value_or(-1); run_application(diagnostics_uploader.string(), args, [this, &log_status](const std::string& output_line) { if (output_line == "Uploaded") { log_status.log_status = types::system::string_to_log_status_enum(output_line); } else if (output_line == "UploadFailure" || output_line == "PermissionDenied" || output_line == "BadMessage" || output_line == "NotSupportedOperation") { log_status.log_status = types::system::LogStatusEnum::UploadFailure; } else { log_status.log_status = types::system::LogStatusEnum::Uploading; } this->publish_log_status(log_status); if (this->interrupt_log_upload) { return CmdControl::Terminate; } return CmdControl::Continue; }); if (this->interrupt_log_upload) { EVLOG_info << "Uploading Logs was interrupted, terminating upload script, requestId: " << log_status.request_id; // N01.FR.20 log_status.log_status = types::system::LogStatusEnum::AcceptedCanceled; this->publish_log_status(log_status); } else if (log_status.log_status != types::system::LogStatusEnum::Uploaded && retries < total_retries) { // command finished, but neither interrupted nor uploaded std::this_thread::sleep_for(std::chrono::seconds(retry_interval)); } else { uploaded = true; } } this->log_upload_running = false; this->log_upload_cv.notify_one(); EVLOG_info << "Log upload thread finished"; }); this->upload_logs_thread.detach(); return response; }; bool systemImpl::handle_is_reset_allowed(types::system::ResetType& type) { // Right now we dont want to reject a reset ever return true; } void systemImpl::handle_reset(types::system::ResetType& type, bool& scheduled) { // let the actual work be done by a worker thread, which can also delay it // a little bit (if configured) to allow e.g. clean shutdown of communication // channels in parallel when this call returns std::thread([this, type, scheduled] { EVLOG_info << "Reset request received: " << type << ", " << (scheduled ? "" : "not ") << "scheduled"; if (!this->mod->r_store.empty() and !this->mod->r_store.at(0)->call_exists(boot_reason_key)) { this->mod->r_store.at(0)->call_store(boot_reason_key, boot_reason_to_string(types::system::BootReason::RemoteReset)); } std::this_thread::sleep_for(std::chrono::seconds(this->mod->config.ResetDelay)); if (type == types::system::ResetType::Soft) { EVLOG_info << "Performing soft reset now."; kill(getpid(), SIGINT); } else { EVLOG_info << "Performing hard reset now."; kill(getpid(), SIGINT); // FIXME(piet): Define appropriate behavior for hard reset } }).detach(); } bool systemImpl::handle_set_system_time(std::string& timestamp) { // your code for cmd set_system_time goes here return true; }; types::system::BootReason systemImpl::handle_get_boot_reason() { if (this->mod->r_store.empty()) { return types::system::BootReason::PowerUp; } auto reason_variant = this->mod->r_store.at(0)->call_load(boot_reason_key); auto* reason = std::get_if(&reason_variant); std::string final_reason{boot_reason_to_string(types::system::BootReason::PowerUp)}; if (reason != nullptr) { final_reason = *reason; } this->mod->r_store.at(0)->call_delete(boot_reason_key); return types::system::string_to_boot_reason(final_reason); } } // namespace main } // namespace module