1#include "service.hpp"
2
3#include "ServiceManager.hpp"
4#include "global.hpp"
5#include "utils/ExecUtils.hpp"
6
7RegisterUnit(service, Service);
8RegisterUnit(driver, Service);
9
10ServiceOptions::ServiceOptions(toml::node_view<toml::node> table_in)
11{
12 if (!table_in)
13 return;
14
15 if (!table_in.is_table())
16 {
17 std::cerr << "service: bad 'service' options" << std::endl;
18 return;
19 }
20
21 auto &table = *table_in.as_table();
22 const std::string state_change = table["state-change"].value_or(default_value: "immediate");
23 table.erase(key: "state-change");
24
25 {
26 if (state_change == "immediate")
27 stateChangeNotifyType = StateChangeNotifyType::Immediate;
28 else if (state_change == "notify")
29 stateChangeNotifyType = StateChangeNotifyType::Notify;
30 else
31 std::cerr << "service: bad state-change" << std::endl;
32 }
33
34 // warn if table["service"] contains unknown keys
35 for (const auto &kv : table)
36 std::cerr << "service: unknown key " << kv.first << std::endl;
37}
38
39Service::Service(const std::string &id, toml::table &table, std::shared_ptr<const Template> template_, const ArgumentMap &args)
40 : Unit(id, table, template_, args), service_options(table["service"])
41{
42 table.erase(key: "service");
43
44 if (table["options"]["exec"].is_string())
45 exec.push_back(x: PopArg(table, key: "exec"));
46 else if (table["options"]["exec"].is_array())
47 exec = GetArrayArg(table, key: "exec");
48 else
49 std::cerr << "service " << id << ": bad exec" << std::endl;
50}
51
52bool Service::Start()
53{
54 status.Starting(msg: "starting...");
55 token = ExecUtils::GetRandomString();
56 const auto pid = ExecUtils::DoFork(exec, token, baseId: GetBaseId());
57 if (pid < 0)
58 {
59 std::cerr << "failed to start service " << id << std::endl;
60 status.Failed(msg: "failed");
61 return false;
62 }
63
64 main_pid = pid;
65 if (service_options.stateChangeNotifyType == StateChangeNotifyType::Immediate)
66 {
67 status.Started(msg: "running");
68 ServiceManager->OnUnitStarted(unit: this);
69 }
70
71 return true;
72}
73
74bool Service::Stop()
75{
76 status.Stopping(msg: "stopping...");
77 std::cout << "stopping service " << id << std::endl;
78 const int pid = main_pid;
79 if (pid == -1)
80 {
81 std::cerr << "service " << id << " not running" << std::endl;
82 status.Inactive();
83 return true;
84 }
85
86 kill(pid: pid, SIGTERM);
87 return true;
88}
89
90void Service::onPrint(std::ostream &os) const
91{
92 os << " exec: ";
93 for (const auto &e : this->exec)
94 os << e << " ";
95 os << std::endl;
96 if (this->status.status == UnitStatus::UnitFailed)
97 os << "failed: " << this->status.message << ", exit status: " << this->exit_status;
98 os << std::endl;
99}
100
101void Service::OnExited(int status)
102{
103 status = status == W_EXITCODE(0, SIGTERM) ? 0 : status;
104 this->exit_status = status;
105 if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
106 {
107 this->status.Inactive();
108 }
109 else if (WIFEXITED(status))
110 {
111 std::cout << "service " << id << " exited with status " << WEXITSTATUS(status) << std::endl;
112 this->status.Failed(msg: "exitcode: " + std::to_string(WEXITSTATUS(status)));
113 }
114 else if (WIFSIGNALED(status))
115 {
116 this->status.Failed(msg: "terminated by signal: " + std::to_string(WTERMSIG(status)));
117 }
118 else
119 {
120 this->status.Failed(msg: "unknown exit status: " + std::to_string(val: status));
121 }
122 ServiceManager->OnUnitStopped(unit: this);
123}
124
125void Service::ChangeState(const UnitStatus &status)
126{
127 if (service_options.stateChangeNotifyType != StateChangeNotifyType::Notify)
128 {
129 std::cerr << "service " << id << " does not support state change notification" << std::endl;
130 return;
131 }
132
133 const auto prev_status = this->status;
134 this->status = status;
135
136 // TODO: handle state change
137 std::cerr << C_YELLOW << "service " << id << " state change: " << prev_status.status << " -> " << status.status << C_RESET << std::endl;
138
139 if (status.status == UnitStatus::UnitStarted)
140 ServiceManager->OnUnitStarted(unit: this);
141}
142