1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "proto/services.pb.h"
4#include "proto/services.service.h"
5
6#include <algorithm>
7#include <iostream>
8#include <librpc/rpc.h>
9#include <memory>
10#include <pb_decode.h>
11#include <vector>
12
13#define RED(text) "\033[1;31m" text "\033[0m"
14#define GREEN(text) "\033[1;32m" text "\033[0m"
15#define YELLOW(text) "\033[1;33m" text "\033[0m"
16#define GRAY(text) "\033[1;30m" text "\033[0m"
17#define WHITE(text) "\033[1;37m" text "\033[0m"
18
19#define C_RED "\033[1;31m"
20#define C_GREEN "\033[1;32m"
21#define C_YELLOW "\033[1;33m"
22#define C_BLUE "\033[1;34m"
23#define C_GRAY "\033[1;30m"
24#define C_WHITE "\033[1;37m"
25#define C_RESET "\033[0m"
26
27using namespace std::string_literals;
28
29constexpr auto SERVICE_MANAGER_RPC_NAME = "mos.service_manager";
30const auto ServiceManager = std::make_shared<ServiceManagerStub>(args: SERVICE_MANAGER_RPC_NAME);
31
32struct Command
33{
34 const std::string_view name;
35 const std::string_view description;
36 int (*const handler)(int, char *[]);
37};
38
39static const char *GetStatusColor(const struct _RpcUnit &unit)
40{
41 if (!unit.status.isActive)
42 return C_GRAY;
43 switch (unit.status.status)
44 {
45 case RpcUnitStatusEnum_Starting: return C_YELLOW;
46 case RpcUnitStatusEnum_Started: return C_GREEN;
47 case RpcUnitStatusEnum_Failed: return C_RED;
48 case RpcUnitStatusEnum_Stopping: return C_YELLOW;
49 case RpcUnitStatusEnum_Stopped: return C_RED;
50 }
51
52 __builtin_unreachable();
53}
54
55static std::string GetUnitStatus(const struct _RpcUnit &unit)
56{
57 if (!unit.status.isActive)
58 return "inactive";
59
60 switch (unit.status.status)
61 {
62 case RpcUnitStatusEnum_Starting: return "starting";
63 case RpcUnitStatusEnum_Started: return "started";
64 case RpcUnitStatusEnum_Failed: return "failed";
65 case RpcUnitStatusEnum_Stopping: return "stopping";
66 case RpcUnitStatusEnum_Stopped: return "stopped";
67 }
68
69 __builtin_unreachable();
70}
71
72static const char *GetUnitType(const struct _RpcUnit &unit)
73{
74 switch (unit.type)
75 {
76 case RpcUnitType_Service: return "Service"; break;
77 case RpcUnitType_Target: return "Target"; break;
78 case RpcUnitType_Path: return "Path"; break;
79 case RpcUnitType_Mount: return "Mount"; break;
80 case RpcUnitType_Symlink: return "SymLink"; break;
81 case RpcUnitType_Device: return "Device"; break;
82 case RpcUnitType_Timer: return "Timer"; break;
83 }
84
85 __builtin_unreachable();
86}
87
88const int UnitNameLength = 35;
89
90static int UpdateMaxUnitNameLength(pb_size_t units_count, RpcUnit *units)
91{
92 int max = UnitNameLength;
93 for (size_t i = 0; i < units_count; i++)
94 max = std::max(a: max, b: int(strlen(s: units[i].name)));
95
96 return max + 4;
97}
98
99static int do_list(int, char **)
100{
101 GetUnitsRequest req;
102 GetUnitsResponse resp;
103 ServiceManager->get_units(request: &req, response: &resp);
104 int unitNameLen = UpdateMaxUnitNameLength(units_count: resp.units_count, units: resp.units);
105
106 printf(C_WHITE " %-*s %-10s %-30s %-31s %-30s\n" C_RESET, unitNameLen, "Unit Name", "Type", "Status", "Since", "Description");
107 const std::string line(2 + unitNameLen + 1 + 10 + 1 + 30 + 1 + 31 + 2 + 30, '=');
108 puts(string: line.c_str());
109
110 for (size_t i = 0; i < resp.units_count; i++)
111 {
112 const auto &unit = resp.units[i];
113 const auto color = GetStatusColor(unit);
114
115 std::string statusmsg = GetUnitStatus(unit);
116 if (unit.status.isActive)
117 statusmsg += std::string(" (") + unit.status.statusMessage + ")";
118
119 std::string ctime = std::ctime(timer: &unit.status.timestamp);
120 ctime.erase(pos: ctime.length() - 1); // remove newline
121
122 printf(format: " %s●" C_RESET " %-*s " C_YELLOW "%-10s" C_RESET " %s%-30s%s %s%-*s%s %s\n", //
123 color,
124 unitNameLen - 2, //
125 unit.name, //
126 GetUnitType(unit), //
127 color, //
128 statusmsg.c_str(), //
129 C_RESET, //
130 unit.status.isActive ? C_WHITE : C_GRAY, //
131 int(ctime.size() + 7), //
132 ctime.c_str(), //
133 C_RESET, //
134 unit.description //
135 );
136
137 for (auto j = 0u; j < unit.overridden_units_count; j++)
138 {
139 const auto &overridden = unit.overridden_units[j];
140 printf(format: " " C_GRAY "%s └─ %s\n", std::string(3 * j, ' ').c_str(), overridden.base_unit_id);
141 }
142 }
143
144 return 0;
145}
146
147static int do_list_templates(int, char **)
148{
149 GetTemplatesRequest req;
150 GetTemplatesResponse resp;
151 ServiceManager->get_templates(request: &req, response: &resp);
152
153 // print header
154 constexpr auto ArgsLength = 25;
155 constexpr auto ParamsLength = 15;
156 const int n = printf(C_WHITE " %-*s %-*s%-*s%-*s\n" C_RESET, //
157 UnitNameLength, "Template Name", //
158 ArgsLength, "Predefined Args", //
159 ParamsLength, "Parameters", //
160 60, "Description");
161
162 printf(format: "%s\n", std::string(n, '=').c_str());
163
164 for (size_t i = 0; i < resp.templates_count; i++)
165 {
166 const auto &template_ = resp.templates[i];
167 const auto nlines = std::max(a: 1u, b: std::max(a: template_.parameters_count, b: template_.predefined_arguments_count));
168
169 for (size_t j = 0; j < nlines; j++)
170 {
171 if (j == 0)
172 {
173 // print first line
174 printf(C_GREEN " ●" C_RESET " %-*s" C_RESET, UnitNameLength - 1, template_.base_id);
175 }
176 else
177 {
178 printf(format: " %-*s", UnitNameLength + 1, "");
179 }
180
181 std::string argument;
182 if (j < template_.predefined_arguments_count)
183 argument = std::string(C_YELLOW) + template_.predefined_arguments[j].name + C_RESET " = " C_GREEN + template_.predefined_arguments[j].value + C_RESET;
184 else if (j == 0)
185 argument = "None";
186
187 std::string parameter;
188 if (j < template_.parameters_count)
189 parameter = std::string(C_YELLOW) + template_.parameters[j] + C_RESET;
190 else if (j == 0)
191 parameter = "None";
192
193 const int argument_color_length = j < template_.predefined_arguments_count ? strlen(C_YELLOW) + strlen(C_RESET) + strlen(C_GREEN) + strlen(C_RESET) : 0;
194 const int parameter_color_length = j < template_.parameters_count ? strlen(C_YELLOW) + strlen(C_RESET) : 0;
195
196 printf(C_YELLOW "%-*s" C_RESET, ArgsLength + argument_color_length, argument.c_str());
197 printf(C_YELLOW "%-*s" C_RESET, ParamsLength + parameter_color_length, parameter.c_str());
198
199 if (j == 0)
200 printf(C_RESET "%s", template_.description);
201 puts(string: "");
202 }
203 printf(C_RESET "%s\n" C_RESET, std::string(n, '-').c_str());
204 }
205 return 0;
206}
207
208static int do_list_overrides(int, char **)
209{
210 GetUnitOverridesRequest req;
211 GetUnitOverridesResponse resp;
212 ServiceManager->get_unit_overrides(request: &req, response: &resp);
213
214 // header line
215 const auto n = printf(C_WHITE " %-*s %-*s\n" C_RESET, //
216 UnitNameLength, "Overridden Unit", //
217 UnitNameLength, "Base Unit & Predefined Args");
218
219 printf(format: "%s\n", std::string(n, '=').c_str());
220 for (size_t i = 0; i < resp.overrides_count; i++)
221 {
222 const auto &override = resp.overrides[i];
223 printf(C_GREEN " ● " C_RESET "%s%-*s%s%s%-*s%s\n", //
224 C_RESET, UnitNameLength - 1, override.overridden_unit_id, C_RESET, //
225 C_WHITE, UnitNameLength, override.base_unit_id, C_RESET //
226 );
227
228 for (size_t j = 0; j < override.overrides_count; j++)
229 {
230 const auto &param = override.overrides[j];
231 printf(format: " %-*s" C_YELLOW "%s" C_RESET " = " C_GREEN "%s" C_RESET "\n", UnitNameLength - 1, "", param.name, param.value);
232 }
233 printf(C_RESET "%s\n", std::string(n, '-').c_str());
234 }
235 return 0;
236}
237
238static int do_start_unit(int argc, char **argv)
239{
240 if (argc != 1)
241 {
242 std::cerr << "Usage: sc start <unit_id>" << std::endl;
243 return 1;
244 }
245
246 StartUnitRequest req;
247 req.unit_id = argv[0];
248 StartUnitResponse resp;
249 const auto err = ServiceManager->start_unit(request: &req, response: &resp);
250
251 if (err != RPC_RESULT_OK)
252 {
253 std::cerr << "Failed to start unit: error " << err << std::endl;
254 return 1;
255 }
256 return 0;
257}
258
259static int do_stop_unit(int argc, char **argv)
260{
261 if (argc != 1)
262 {
263 std::cerr << "Usage: sc stop <unit_id>" << std::endl;
264 return 1;
265 }
266
267 StopUnitRequest req;
268 req.unit_id = argv[0];
269 StopUnitResponse resp;
270 const auto err = ServiceManager->stop_unit(request: &req, response: &resp);
271
272 if (err != RPC_RESULT_OK)
273 {
274 std::cerr << "Failed to stop unit: error " << err << std::endl;
275 return 1;
276 }
277 return 0;
278}
279
280static int do_instantiate(int argc, char **argv)
281{
282 if (argc < 1)
283 {
284 std::cerr << "Usage: sc instantiate <template_id> [param1=value1] [param2=value2] ..." << std::endl;
285 return 1;
286 }
287
288 InstantiateUnitRequest req;
289 req.template_id = argv[0];
290 req.parameters_count = argc - 1;
291 req.parameters = static_cast<KeyValuePair *>(malloc(size: argc * sizeof(KeyValuePair)));
292 for (int i = 1; i < argc; i++)
293 {
294 const auto &param = argv[i];
295 const auto eq = strchr(s: param, c: '=');
296 if (!eq)
297 {
298 std::cerr << "Invalid parameter: " << param << std::endl;
299 return 1;
300 }
301
302 req.parameters[i - 1].name = strndup(string: param, max_size: eq - param);
303 req.parameters[i - 1].value = strdup(string: eq + 1);
304 }
305
306 for (int i = 0; i < argc - 1; i++)
307 std::cout << "param " << req.parameters[i].name << " = " << req.parameters[i].value << std::endl;
308
309 InstantiateUnitResponse resp;
310 const auto err = ServiceManager->instantiate_unit(request: &req, response: &resp);
311
312 if (err != RPC_RESULT_OK)
313 std::cerr << "Failed to instantiate unit: error " << err << std::endl;
314 else
315 std::cout << "Unit instantiated: " << resp.unit_id << std::endl;
316 return err;
317}
318
319static int do_listall(int, char **)
320{
321 puts(string: "List of current units:");
322 do_list(0, nullptr);
323 puts(string: "");
324 puts(string: "List of current templates:");
325 do_list_templates(0, nullptr);
326 puts(string: "");
327 puts(string: "List of current unit overrides:");
328 do_list_overrides(0, nullptr);
329 return 0;
330}
331
332static int do_help(int, char *[]);
333
334const std::vector<Command> commands = {
335 { .name: "list", .description: "List all services", .handler: do_list },
336 { .name: "listt", .description: "List all templates", .handler: do_list_templates },
337 { .name: "listo", .description: "List all unit overrides", .handler: do_list_overrides },
338 { .name: "listall", .description: "List all services and templates", .handler: do_listall },
339 { .name: "start", .description: "Start unit", .handler: do_start_unit },
340 { .name: "stop", .description: "Stop unit", .handler: do_stop_unit },
341 { .name: "instantiate", .description: "Instantiate unit from template", .handler: do_instantiate },
342 { .name: "help", .description: "Show help", .handler: do_help },
343};
344
345static int do_help(int, char **)
346{
347 std::cout << "Usage: " << program_invocation_name << " <command> [args...]" << std::endl;
348 std::cout << "Commands:" << std::endl;
349 for (const auto &cmd : commands)
350 std::cout << " " << cmd.name << " - " << cmd.description << std::endl;
351 return 0;
352}
353
354int main(int argc, char **argv)
355{
356 if (argc == 1)
357 {
358 do_list(0, nullptr);
359 return 0;
360 }
361
362 if (argc == 2 && (strcmp(a: argv[1], b: "--help") == 0 || strcmp(a: argv[1], b: "-h") == 0))
363 {
364 do_help(argc, argv);
365 return 0;
366 }
367
368 const auto command = argv[1];
369 const auto it = std::find_if(first: commands.begin(), last: commands.end(), pred: [&](const auto &cmd) { return cmd.name == command; });
370 if (it == commands.end())
371 {
372 std::cout << "Unknown command: " << command << std::endl;
373 return 1;
374 }
375
376 return it->handler(argc - 2, argv + 2);
377}
378