diff --git a/src/Service.cpp b/src/Service.cpp new file mode 100644 index 0000000..25d839b --- /dev/null +++ b/src/Service.cpp @@ -0,0 +1,219 @@ +#include "Service.hpp" + +fc::Service::Service(const path &config_path, bool daemon) + : controller(config_path) { + if (daemon) + daemonize(); +} + +fc::Service::~Service() { + disable_controller(); + if (server) + server->Shutdown(); +} + +void fc::Service::run() { + try { + if (service_running()) { + LOG(llvl::fatal) << "Only 1 instance may be run"; + return; + } + + // TODO: use SSL + // auto ssl_options = grpc::SslServerCredentialsOptions(); + // ... + // auto creds = grpc::SslServerCredentials(ssl_options); + + auto creds = grpc::InsecureServerCredentials(); + ServerBuilder builder; + server = builder.AddListeningPort(SERVICE_ADDR, creds) + .RegisterService(this) + .BuildAndStart(); + + if (server) { + enable_controller(); + server->Wait(); + } + } catch (std::exception &e) { + LOG(llvl::fatal) << e.what(); + throw e; + } +} + +void fc::Service::enable_controller() { + if (controller.disabled()) + controller_thread = thread([this] { controller.enable(); }); +} + +void fc::Service::disable_controller() { + controller.disable(); + controller_thread.interrupt(); +} + +Status fc::Service::StopService([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + [[maybe_unused]] fc_pb::Empty *resp) { + disable_controller(); + thread([&] { server->Shutdown(); }).detach(); + return Status::OK; +} + +Status fc::Service::Enable([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + [[maybe_unused]] fc_pb::Empty *resp) { + enable_controller(); + return Status::OK; +} + +Status fc::Service::Disable([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + [[maybe_unused]] fc_pb::Empty *resp) { + disable_controller(); + return Status::OK; +} + +Status fc::Service::Reload([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + [[maybe_unused]] fc_pb::Empty *resp) { + controller.reload(); + return Status::OK; +} + +Status fc::Service::NvInit([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + [[maybe_unused]] fc_pb::Empty *resp) { + controller.nv_init(); + return Status::OK; +} + +Status fc::Service::ControllerStatus([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + fc_pb::ControllerState *resp) { + auto s = static_cast(controller.state); + resp->set_state(s); + return Status::OK; +} + +Status fc::Service::GetDevices([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + fc_pb::Devices *devices) { + controller.devices.to(*devices); + return Status::OK; +} + +Status fc::Service::SetDevices([[maybe_unused]] ServerContext *context, + const fc_pb::Devices *devices, + [[maybe_unused]] fc_pb::Empty *e) { + controller.devices = fc::Devices(); + controller.devices.from(*devices); + return Status::OK; +} + +Status +fc::Service::GetEnumeratedDevices([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *req, + fc_pb::Devices *devices) { + Devices(true).to(*devices); + return Status::OK; +} + +Status fc::Service::Test([[maybe_unused]] ServerContext *context, + const fc_pb::TestRequest *e, + ServerWriter *writer) { + auto fit = controller.devices.fans.find(e->device_label()); + if (fit == controller.devices.fans.end()) + // TODO: error << device isn't found + return Status::OK; + + auto cb = [&](int &status) { + fc_pb::TestResponse resp; + resp.set_status(status); + if (status == 100) + writer->WriteLast(resp, grpc::WriteOptions()); + else + writer->Write(resp); + }; + + controller.test(*fit->second, e->forced(), cb); + + return Status::OK; +} + +Status fc::Service::GetControllerConfig([[maybe_unused]] ServerContext *context, + [[maybe_unused]] const fc_pb::Empty *e, + fc_pb::ControllerConfig *config) { + controller.to(*config); + return Status::OK; +} + +Status fc::Service::SetControllerConfig([[maybe_unused]] ServerContext *context, + const fc_pb::ControllerConfig *config, + [[maybe_unused]] fc_pb::Empty *e) { + controller.from(*config); + controller.reload(); + return Status::OK; +} + +void fc::Service::daemonize() { + const auto fork_thread = []() { + pid_t pid = fork(); + + // On success: child's PID is returned in parent, 0 returned in child + if (pid >= 0) + exit(EXIT_SUCCESS); + else if (pid == -1) { // On failure: -1 returned in parent + LOG(llvl::fatal) << "Failed to fork off parent"; + exit(EXIT_FAILURE); + } + }; + + // Daemonize process by forking, creating new session, then forking again + fork_thread(); + + // Create a new session for the child + if (setsid() < 0) { + LOG(llvl::fatal) << "Failed to fork off parent"; + exit(EXIT_FAILURE); + } + + fork_thread(); + + // Redirect standard file descriptors to /dev/null + const char *dnull = "/dev/null"; + stdin = fopen(dnull, "r"); + stdout = fopen(dnull, "r+"); + stderr = fopen(dnull, "r+"); + + // 664 (rw-rw-r--) files; 775 (rwxrwxr-x) directories + umask(002); + + // Set working dir to / + if (chdir("/") < 0) + LOG(llvl::error) << "Failed to set working directory to '/'"; +} + +bool fc::Service::service_running() { + auto creds = grpc::InsecureChannelCredentials(); + auto channel = grpc::CreateChannel(Util::SERVICE_ADDR, creds); + channel->WaitForConnected(Util::deadline(200)); + return channel->GetState(true) == GRPC_CHANNEL_READY; +} + +// void fc::Service::signal_handler(int signal) { +// switch (signal) { +// case SIGINT: +// case SIGQUIT: +// case SIGTERM: +// fc::Controller::disable(); +// SERVER->Shutdown(); +// break; +// default: +// LOG(llvl::warning) << "Unhandled signal (" << signal +// << "): " << strsignal(signal); +// } +//} +// +// void fc::Service::register_signal_handler() { +// for (const auto &s : {SIGINT, SIGQUIT, SIGTERM, SIGUSR1}) +// std::signal(s, &fc::Service::signal_handler); +//} diff --git a/src/Service.hpp b/src/Service.hpp new file mode 100644 index 0000000..9f18648 --- /dev/null +++ b/src/Service.hpp @@ -0,0 +1,82 @@ +#ifndef FANCON_SERVICE_HPP +#define FANCON_SERVICE_HPP + +#include "Controller.hpp" +#include +#include +#include +#include +#include +#include + +#include "proto/DevicesSpec.grpc.pb.h" +#include "proto/DevicesSpec.pb.h" + +using fc::Controller; +using fc::Util::SERVICE_ADDR; +using fc_pb::Empty; +using grpc::ChannelCredentials; +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::ServerReader; +using grpc::ServerReaderWriter; +using grpc::ServerWriter; +using grpc::Status; +using std::lock_guard; +using std::mutex; + +namespace fc { +class Service : public fc_pb::DService::Service { +public: + explicit Service(const path &config_path, bool daemon = false); + ~Service() override; + + void run(); + + Status StopService(ServerContext *context, const fc_pb::Empty *e, + fc_pb::Empty *resp) override; + Status Enable(ServerContext *context, const fc_pb::Empty *e, + fc_pb::Empty *resp) override; + Status Disable(ServerContext *context, const fc_pb::Empty *e, + fc_pb::Empty *resp) override; + Status Reload(ServerContext *context, const fc_pb::Empty *e, + fc_pb::Empty *resp) override; + Status NvInit(ServerContext *context, const fc_pb::Empty *e, + fc_pb::Empty *resp) override; + Status ControllerStatus(ServerContext *context, const fc_pb::Empty *e, + fc_pb::ControllerState *resp) override; + + Status GetDevices(ServerContext *context, const fc_pb::Empty *e, + fc_pb::Devices *devices) override; + Status SetDevices(ServerContext *context, const fc_pb::Devices *devices, + fc_pb::Empty *e) override; + Status GetEnumeratedDevices(ServerContext *context, const fc_pb::Empty *req, + fc_pb::Devices *devices) override; + Status Test(ServerContext *context, const fc_pb::TestRequest *e, + ServerWriter *writer) override; + + Status GetControllerConfig(ServerContext *context, const fc_pb::Empty *e, + fc_pb::ControllerConfig *config) override; + Status SetControllerConfig(ServerContext *context, + const fc_pb::ControllerConfig *config, + fc_pb::Empty *e) override; + +private: + fc::Controller controller; + unique_ptr server; + thread controller_thread; + mutex test_writer_mutex; + + void enable_controller(); + void disable_controller(); + static void daemonize(); + + static bool service_running(); + + // static void signal_handler(int signal); + // static void register_signal_handler(); +}; +} // namespace fc + +#endif // FANCON_SERVICE_HPP