4e9f147e55
Adds pawlethwd, a native C++ system_ext daemon (coredomain) that detects peripherals, checks the hardware update catalog (ota.php?mode=hardware), verifies payloads, and flashes firmware via authorized handler plugins. - daemon/: manifest parse, libcurl HTTP + sha256, sysfs USB detection, catalog client, dlopen handler registry (fixed path + ABI + allowlist), state persistence, orchestration engine, AIDL service, main. - aidl/: IPawletHardwareUpdate (getComponents/getPendingUpdates/checkNow/...). - include/pawlet/hwh_abi.h: stable C plugin ABI. - handlers/usbdfu/: libpawlethwh_usbdfu.so, DFU 1.1 download over libusb. - sepolicy/: pawlethwd domain, pawlet_hw_data_file, service type. - schemas/, etc/supported_hardware.default.json, README design doc. - hwupdate.mk wires packages + ro.pawlet.hardware.manifest + sepolicy dir; inherited from config/common.mk. Trust: a server-named installer handler is honored only if allowlisted in the immutable supported_hardware.json, maps to a fixed dm-verity-protected .so path, is ABI/id-checked, and every payload is sha256-verified before a handler runs.
219 lines
7.1 KiB
C++
219 lines
7.1 KiB
C++
/*
|
|
* Copyright (C) 2026 oxmc / PawletOS
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include "engine.h"
|
|
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
|
|
#include <android-base/logging.h>
|
|
|
|
#include "catalog.h"
|
|
#include "detect.h"
|
|
#include "http.h"
|
|
#include "manifest.h"
|
|
#include "pawlet/hwh_abi.h"
|
|
#include "util.h"
|
|
|
|
namespace pawlet::hwupdate {
|
|
namespace {
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
bool Contains(const std::vector<std::string>& v, const std::string& s) {
|
|
return std::find(v.begin(), v.end(), s) != v.end();
|
|
}
|
|
|
|
void ProgressCb(int percent, void* user) {
|
|
const char* id = static_cast<const char*>(user);
|
|
LOG(INFO) << "hwupdate: flashing " << (id ? id : "?") << " " << percent << "%";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Engine::Engine(EngineConfig cfg) : cfg_(std::move(cfg)), state_(cfg_.stateDir + "/state.json") {}
|
|
|
|
Engine::~Engine() { Stop(); }
|
|
|
|
void Engine::Start() {
|
|
std::error_code ec;
|
|
fs::create_directories(cfg_.stateDir, ec);
|
|
fs::create_directories(cfg_.stateDir + "/downloads", ec);
|
|
worker_ = std::thread([this] { WorkerLoop(); });
|
|
CheckNow(); // initial cycle at boot
|
|
}
|
|
|
|
void Engine::Stop() {
|
|
{
|
|
std::lock_guard<std::mutex> lk(reqMu_);
|
|
if (stop_) return;
|
|
stop_ = true;
|
|
reqCv_.notify_all();
|
|
}
|
|
if (worker_.joinable()) worker_.join();
|
|
}
|
|
|
|
void Engine::CheckNow() {
|
|
std::lock_guard<std::mutex> lk(reqMu_);
|
|
requested_ = true;
|
|
reqCv_.notify_all();
|
|
}
|
|
|
|
void Engine::WorkerLoop() {
|
|
for (;;) {
|
|
{
|
|
std::unique_lock<std::mutex> lk(reqMu_);
|
|
reqCv_.wait(lk, [this] { return requested_ || stop_; });
|
|
if (stop_) return;
|
|
requested_ = false;
|
|
}
|
|
busy_.store(true);
|
|
int rc = RunCycle();
|
|
lastResult_.store(rc);
|
|
busy_.store(false);
|
|
}
|
|
}
|
|
|
|
int Engine::RunCycle() {
|
|
auto manifest = LoadManifest(cfg_.manifestPath);
|
|
if (!manifest) return -1;
|
|
|
|
state_.Load();
|
|
std::vector<DetectedComponent> detected = Detect(*manifest);
|
|
|
|
std::vector<ComponentStatus> comps;
|
|
std::vector<PendingUpdate> pend;
|
|
|
|
for (auto& dc : detected) {
|
|
const ManifestProduct* prod = dc.product;
|
|
const ManifestComponent* comp = dc.comp;
|
|
|
|
ComponentStatus cs;
|
|
cs.componentId = comp->id;
|
|
cs.productId = prod->id;
|
|
cs.componentClass = comp->cls;
|
|
cs.role = comp->role;
|
|
cs.transport = dc.transport;
|
|
cs.present = dc.present;
|
|
cs.hardwareRevision = dc.hardwareRev;
|
|
cs.support = comp->support;
|
|
|
|
if (!dc.present) {
|
|
comps.push_back(std::move(cs));
|
|
continue;
|
|
}
|
|
|
|
// Resolve a usable handler + read the installed version.
|
|
const pawlet_hwh_api* handler = nullptr;
|
|
for (const auto& hid : comp->installerHandlers) {
|
|
handler = handlers_.Get(hid);
|
|
if (handler) break;
|
|
}
|
|
|
|
pawlet_hw_ctx ctx{};
|
|
ctx.component_id = comp->id.c_str();
|
|
ctx.transport = dc.transport.c_str();
|
|
ctx.usb_vendor_id = dc.usbVendorId.c_str();
|
|
ctx.usb_product_id = dc.usbProductId.c_str();
|
|
ctx.dev_path = dc.devPath.c_str();
|
|
ctx.hardware_rev = dc.hardwareRev.c_str();
|
|
|
|
std::string installed = state_.InstalledVersion(comp->id);
|
|
if (handler) {
|
|
char ver[128] = {0};
|
|
if (handler->get_version(&ctx, ver, sizeof(ver)) == PAWLET_HWH_OK && ver[0]) {
|
|
installed = ver;
|
|
}
|
|
}
|
|
cs.installedVersion = installed;
|
|
|
|
// Ask the server for the newest compatible update.
|
|
CheckRequest req;
|
|
req.device = manifest->device;
|
|
req.pawletVersion = manifest->buildVersion;
|
|
req.hardwareId = prod->id;
|
|
req.componentId = comp->id;
|
|
req.hardwareRevision = dc.hardwareRev;
|
|
req.currentVersion = installed;
|
|
|
|
auto update = CheckUpdate(cfg_.serverBaseUrl, req);
|
|
|
|
PendingUpdate pu;
|
|
pu.componentId = comp->id;
|
|
if (update && update->version != installed) {
|
|
pu.available = true;
|
|
pu.version = update->version;
|
|
pu.sizeBytes = update->sizeBytes;
|
|
pu.changelogUrl = update->changelogUrl;
|
|
|
|
// Trust boundary: the server-named handler MUST be allowlisted for
|
|
// this component in the immutable manifest.
|
|
if (!Contains(comp->installerHandlers, update->installerHandler)) {
|
|
LOG(ERROR) << "hwupdate: refusing non-allowlisted handler '"
|
|
<< update->installerHandler << "' for " << comp->id;
|
|
} else if (cfg_.autoApply) {
|
|
if (state_.RetryCount(comp->id) >= cfg_.maxRetries) {
|
|
LOG(WARNING) << "hwupdate: " << comp->id << " exceeded retries; skipping";
|
|
} else {
|
|
const pawlet_hwh_api* flashHandler = handlers_.Get(update->installerHandler);
|
|
std::string dl = cfg_.stateDir + "/downloads/" + update->id + "-" +
|
|
update->filename;
|
|
bool ok = flashHandler && http::Download(update->url, dl);
|
|
if (ok) {
|
|
std::string sum = util::Sha256File(dl);
|
|
if (!util::IEquals(sum, update->checksumSha256)) {
|
|
LOG(ERROR) << "hwupdate: checksum mismatch for " << comp->id;
|
|
ok = false;
|
|
}
|
|
}
|
|
if (ok) {
|
|
int r = flashHandler->flash(&ctx, dl.c_str(), ProgressCb,
|
|
const_cast<char*>(comp->id.c_str()));
|
|
if (r == PAWLET_HWH_OK) {
|
|
LOG(INFO) << "hwupdate: flashed " << comp->id << " -> "
|
|
<< update->version;
|
|
state_.SetInstalledVersion(comp->id, update->version);
|
|
state_.SetRetryCount(comp->id, 0);
|
|
cs.installedVersion = update->version;
|
|
pu.available = false; // applied
|
|
} else {
|
|
LOG(ERROR) << "hwupdate: flash failed rc=" << r << " for " << comp->id;
|
|
state_.SetRetryCount(comp->id, state_.RetryCount(comp->id) + 1);
|
|
ok = false;
|
|
}
|
|
} else {
|
|
state_.SetRetryCount(comp->id, state_.RetryCount(comp->id) + 1);
|
|
}
|
|
std::error_code ec;
|
|
fs::remove(dl, ec);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pu.available) pend.push_back(std::move(pu));
|
|
comps.push_back(std::move(cs));
|
|
}
|
|
|
|
state_.Save();
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lk(snapMu_);
|
|
components_ = std::move(comps);
|
|
pending_ = std::move(pend);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::vector<ComponentStatus> Engine::Components() const {
|
|
std::lock_guard<std::mutex> lk(snapMu_);
|
|
return components_;
|
|
}
|
|
|
|
std::vector<PendingUpdate> Engine::Pending() const {
|
|
std::lock_guard<std::mutex> lk(snapMu_);
|
|
return pending_;
|
|
}
|
|
|
|
} // namespace pawlet::hwupdate
|