Files
tactility/Tactility/Source/service/ServiceRegistration.cpp
T
2026-07-26 21:26:52 +02:00

187 lines
6.0 KiB
C++

#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/Mutex.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <tactility/service/service_manager.h>
#include <cassert>
#include <memory>
#include <unordered_map>
namespace tt::service {
constexpr auto* TAG = "ServiceRegistration";
namespace {
// Tracks the heap allocations addService() makes per registered id, so removeService()
// can free them once the kernel confirms the manifest is unregistered. The kernel only
// ever stores the raw pointer it's handed (see service_manager_add/_remove) - it never
// takes ownership - so the registering side (us) is responsible for the lifetime.
struct AllocatedManifest {
std::shared_ptr<const ServiceManifest>* persistentManifest;
::ServiceManifest* cManifest;
};
Mutex& allocatedManifestsMutex() {
static Mutex mutex;
return mutex;
}
std::unordered_map<std::string, AllocatedManifest>& allocatedManifests() {
static std::unordered_map<std::string, AllocatedManifest> map;
return map;
}
} // namespace
// Bridges the kernel's C ServiceManifest/Service callbacks to the C++ Service
// instances they wrap. Declared extern "C" to match the linkage of the C
// function-pointer types they're assigned to (see e.g. gpio_controller.cpp).
extern "C" {
static void cppDestroyServiceTrampoline(const ::ServiceManifest* /*manifest*/, void* data) {
delete static_cast<std::shared_ptr<Service>*>(data);
}
static error_t cppOnStartTrampoline(::ServiceInstance* instance, void* data) {
auto& servicePtr = *static_cast<std::shared_ptr<Service>*>(data);
ServiceContext context(instance);
return servicePtr->onStart(context) ? ERROR_NONE : ERROR_RESOURCE;
}
static void cppOnStopTrampoline(::ServiceInstance* instance, void* data) {
auto& servicePtr = *static_cast<std::shared_ptr<Service>*>(data);
ServiceContext context(instance);
servicePtr->onStop(context);
}
} // extern "C"
void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart) {
assert(manifest != nullptr);
const auto& id = manifest->id;
LOG_I(TAG, "Adding %s", id.c_str());
if (service_manager_find_manifest(id.c_str()) != nullptr) {
LOG_E(TAG, "Service id in use: %s", id.c_str());
return;
}
// Freed by removeService() once the kernel confirms the manifest is unregistered.
// Keeps id's backing string alive for cManifest.id below in the meantime.
auto* persistentManifest = new std::shared_ptr(manifest);
auto* cManifest = new ::ServiceManifest {
.id = (*persistentManifest)->id.c_str(),
.create_service = (*persistentManifest)->createService,
.destroy_service = cppDestroyServiceTrampoline,
.on_start = cppOnStartTrampoline,
.on_stop = cppOnStopTrampoline,
};
{
auto lock = allocatedManifestsMutex().asScopedLock();
lock.lock();
allocatedManifests()[id] = AllocatedManifest { persistentManifest, cManifest };
}
error_t error = service_manager_add(cManifest, autoStart);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to add service %s: %s", id.c_str(), error_to_string(error));
}
}
void addService(const ServiceManifest& manifest, bool autoStart) {
addService(std::make_shared<const ServiceManifest>(manifest), autoStart);
}
bool removeService(const std::string& id) {
if (service_manager_get_state(id.c_str()) != SERVICE_STATE_STOPPED) {
LOG_I(TAG, "Stopping %s before removal", id.c_str());
if (!stopService(id)) {
LOG_E(TAG, "Failed to stop %s before removal", id.c_str());
return false;
}
}
LOG_I(TAG, "Removing %s", id.c_str());
error_t error = service_manager_remove(id.c_str());
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to remove service %s: %s", id.c_str(), error_to_string(error));
return false;
}
// The kernel has confirmed the manifest is unregistered, so id (which points into
// persistentManifest's string) is no longer needed by anything - safe to free now.
{
auto lock = allocatedManifestsMutex().asScopedLock();
lock.lock();
auto iterator = allocatedManifests().find(id);
if (iterator != allocatedManifests().end()) {
delete iterator->second.cManifest;
delete iterator->second.persistentManifest;
allocatedManifests().erase(iterator);
}
}
LOG_I(TAG, "Removed %s", id.c_str());
return true;
}
bool removeService(const ServiceManifest& manifest) {
return removeService(manifest.id);
}
const ::ServiceManifest* findManifestById(const std::string& id) {
return service_manager_find_manifest(id.c_str());
}
bool startService(const std::string& id) {
LOG_I(TAG, "Starting %s", id.c_str());
const error_t error = service_manager_start(id.c_str());
if (error != ERROR_NONE) {
LOG_E(TAG, "Starting %s failed: %s", id.c_str(), error_to_string(error));
return false;
}
LOG_I(TAG, "Started %s", id.c_str());
return true;
}
std::shared_ptr<ServiceContext> findServiceContextById(const std::string& id) {
auto* instance = service_manager_find_instance(id.c_str());
if (instance == nullptr) {
return nullptr;
}
return std::make_shared<ServiceContext>(instance);
}
std::shared_ptr<Service> findServiceById(const std::string& id) {
auto* instance = service_manager_find_instance(id.c_str());
if (instance == nullptr) {
return nullptr;
}
return *static_cast<std::shared_ptr<Service>*>(instance->data);
}
bool stopService(const std::string& id) {
LOG_I(TAG, "Stopping %s", id.c_str());
error_t error = service_manager_stop(id.c_str());
if (error != ERROR_NONE) {
LOG_W(TAG, "Service not running: %s", id.c_str());
return false;
}
LOG_I(TAG, "Stopped %s", id.c_str());
return true;
}
State getState(const std::string& id) {
return service_manager_get_state(id.c_str());
}
} // namespace