| 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 | |
| 27 | using namespace std::string_literals; |
| 28 | |
| 29 | constexpr auto SERVICE_MANAGER_RPC_NAME = "mos.service_manager" ; |
| 30 | const auto ServiceManager = std::make_shared<ServiceManagerStub>(args: SERVICE_MANAGER_RPC_NAME); |
| 31 | |
| 32 | struct Command |
| 33 | { |
| 34 | const std::string_view name; |
| 35 | const std::string_view description; |
| 36 | int (*const handler)(int, char *[]); |
| 37 | }; |
| 38 | |
| 39 | static 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 | |
| 55 | static 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 | |
| 72 | static 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 | |
| 88 | const int UnitNameLength = 35; |
| 89 | |
| 90 | static 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 | |
| 99 | static 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 | |
| 147 | static 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 | |
| 208 | static 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 ¶m = 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 | |
| 238 | static 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 | |
| 259 | static 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 | |
| 280 | static 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 ¶m = 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 | |
| 319 | static 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 | |
| 332 | static int do_help(int, char *[]); |
| 333 | |
| 334 | const 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 | |
| 345 | static 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 | |
| 354 | int 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 | |