blob: 85f62a5be5be35fe8958310ebb742348c16925cb [file] [log] [blame]
Stephan Pleinesf581a072024-05-23 20:59:27 -07001#include <ctype.h>
2#include <stdlib.h>
3
Brian J Griglak6605beb2022-05-23 17:31:57 -06004#include <algorithm>
Tyler Chatowa79419d2020-08-12 20:12:11 -07005#include <chrono>
Stephan Pleinesf581a072024-05-23 20:59:27 -07006#include <compare>
Philipp Schrader08537492021-01-23 16:17:55 -08007#include <functional>
Tyler Chatowa79419d2020-08-12 20:12:11 -07008#include <iostream>
Stephan Pleinesf581a072024-05-23 20:59:27 -07009#include <map>
10#include <memory>
milind upadhyaya87957a2021-03-06 20:46:30 -080011#include <optional>
Stephan Pleinesf581a072024-05-23 20:59:27 -070012#include <string>
milind upadhyay4272f382021-04-07 18:03:08 -070013#include <string_view>
Stephan Pleinesf581a072024-05-23 20:59:27 -070014#include <tuple>
Tyler Chatowa79419d2020-08-12 20:12:11 -070015#include <unordered_map>
Stephan Pleinesf581a072024-05-23 20:59:27 -070016#include <utility>
17#include <vector>
Tyler Chatowa79419d2020-08-12 20:12:11 -070018
Austin Schuh99f7c6a2024-06-25 22:07:44 -070019#include "absl/flags/flag.h"
20#include "absl/log/check.h"
21#include "absl/log/log.h"
Philipp Schrader08537492021-01-23 16:17:55 -080022#include "absl/strings/str_format.h"
James Kuszmaul293b2172021-11-10 16:20:48 -080023#include "absl/strings/str_join.h"
Stephan Pleinesf581a072024-05-23 20:59:27 -070024#include "flatbuffers/string.h"
25#include "flatbuffers/vector.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070026
Stephan Pleinesf581a072024-05-23 20:59:27 -070027#include "aos/configuration.h"
28#include "aos/flatbuffers.h"
Tyler Chatowa79419d2020-08-12 20:12:11 -070029#include "aos/init.h"
Stephan Pleinesf581a072024-05-23 20:59:27 -070030#include "aos/starter/starter_generated.h"
31#include "aos/starter/starter_rpc_generated.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070032#include "aos/starter/starter_rpc_lib.h"
milind upadhyaya87957a2021-03-06 20:46:30 -080033#include "aos/time/time.h"
Tyler Chatowa79419d2020-08-12 20:12:11 -070034
Austin Schuh99f7c6a2024-06-25 22:07:44 -070035ABSL_FLAG(std::string, config, "aos_config.json",
36 "File path of aos configuration");
James Kuszmaul293b2172021-11-10 16:20:48 -080037// TODO(james): Bash autocompletion for node names.
Austin Schuh99f7c6a2024-06-25 22:07:44 -070038ABSL_FLAG(std::string, node, "",
39 "Node to interact with. If empty, just interact with local node.");
40ABSL_FLAG(bool, all_nodes, false, "Interact with all nodes.");
Tyler Chatowa79419d2020-08-12 20:12:11 -070041
Austin Schuh99f7c6a2024-06-25 22:07:44 -070042ABSL_FLAG(bool, _bash_autocomplete, false,
43 "Internal use: Outputs commands or applications for use with "
44 "autocomplete script.");
45ABSL_FLAG(std::string, _bash_autocomplete_word, "",
46 "Internal use: Current word being autocompleted");
47ABSL_FLAG(std::string, sort, "name",
48 "The name of the column to sort processes by. "
49 "Can be \"name\", \"state\", \"pid\", or \"uptime\".");
milind-u08dab882021-10-17 16:24:08 -070050
Philipp Schrader08537492021-01-23 16:17:55 -080051namespace {
Tyler Chatowa79419d2020-08-12 20:12:11 -070052
milind upadhyaya87957a2021-03-06 20:46:30 -080053namespace chrono = std::chrono;
54
Philipp Schrader08537492021-01-23 16:17:55 -080055static const std::unordered_map<std::string, aos::starter::Command>
56 kCommandConversions{{"start", aos::starter::Command::START},
57 {"stop", aos::starter::Command::STOP},
58 {"restart", aos::starter::Command::RESTART}};
Tyler Chatowa79419d2020-08-12 20:12:11 -070059
James Kuszmaul293b2172021-11-10 16:20:48 -080060std::vector<const aos::Node *> InteractNodes(
61 const aos::Configuration *configuration) {
Austin Schuh43fceaf2021-10-16 14:20:22 -070062 if (!configuration->has_nodes()) {
James Kuszmaul293b2172021-11-10 16:20:48 -080063 return {nullptr};
Austin Schuh43fceaf2021-10-16 14:20:22 -070064 }
65
Austin Schuh99f7c6a2024-06-25 22:07:44 -070066 if (!absl::GetFlag(FLAGS_node).empty()) {
67 CHECK(!absl::GetFlag(FLAGS_all_nodes))
68 << "Can't specify both --node and --all_nodes.";
69 return {
70 aos::configuration::GetNode(configuration, absl::GetFlag(FLAGS_node))};
James Kuszmaul293b2172021-11-10 16:20:48 -080071 }
72
Austin Schuh99f7c6a2024-06-25 22:07:44 -070073 if (absl::GetFlag(FLAGS_all_nodes)) {
James Kuszmaul293b2172021-11-10 16:20:48 -080074 return aos::configuration::GetNodes(configuration);
75 }
76
77 return {aos::configuration::GetMyNode(configuration)};
Austin Schuh43fceaf2021-10-16 14:20:22 -070078}
79
James Kuszmaul293b2172021-11-10 16:20:48 -080080std::vector<const aos::Node *> InteractNodesForApplication(
81 const aos::Configuration *config, std::string_view application_name) {
82 const std::vector<const aos::Node *> interact_nodes = InteractNodes(config);
83 std::vector<const aos::Node *> application_nodes;
84 std::vector<std::string> debug_node_names;
85 for (const aos::Node *node : interact_nodes) {
86 if (aos::configuration::GetApplication(config, node, application_name) !=
87 nullptr) {
88 application_nodes.push_back(node);
Austin Schuh43fceaf2021-10-16 14:20:22 -070089 }
James Kuszmaul293b2172021-11-10 16:20:48 -080090 if (node != nullptr) {
91 debug_node_names.push_back(node->name()->str());
92 }
Austin Schuh43fceaf2021-10-16 14:20:22 -070093 }
James Kuszmaul293b2172021-11-10 16:20:48 -080094
95 if (application_nodes.empty()) {
96 if (interact_nodes.size() == 1 && interact_nodes[0] == nullptr) {
97 std::cout << "Unknown application " << application_name << std::endl;
98 } else {
99 std::cout << "Unknown application " << application_name
100 << " on any of node(s) "
101 << absl::StrJoin(debug_node_names, ", ") << std::endl;
102 }
103 }
104 return application_nodes;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700105}
106
milind upadhyaya87957a2021-03-06 20:46:30 -0800107void PrintKey() {
James Kuszmaulce3497e2023-02-26 12:40:26 -0800108 absl::PrintF("%-30s %-10s %-8s %-6s %-9s %-13s\n", "Name", "Node", "State",
109 "PID", "Uptime", "Last Exit Code");
milind upadhyaya87957a2021-03-06 20:46:30 -0800110}
111
Brian J Griglak6605beb2022-05-23 17:31:57 -0600112std::vector<const aos::starter::ApplicationStatus *> SortApplications(
113 const aos::FlatbufferVector<aos::starter::Status> &status) {
114 std::vector<const aos::starter::ApplicationStatus *> sorted_statuses;
115 for (const aos::starter::ApplicationStatus *app_status :
116 *status.message().statuses()) {
117 sorted_statuses.push_back(app_status);
118 }
119 // If --sort flag not set, then return this unsorted vector as is.
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700120 if (absl::GetFlag(FLAGS_sort).empty()) {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600121 return sorted_statuses;
122 }
123
124 // Convert --sort flag to lowercase for testing below.
James Kuszmaulca818162024-07-21 20:56:49 -0700125 std::string sort_on = absl::GetFlag(FLAGS_sort);
126 std::transform(sort_on.begin(), sort_on.end(), sort_on.begin(), tolower);
Brian J Griglak6605beb2022-05-23 17:31:57 -0600127
128 // This function is called once for each node being reported upon, so there is
129 // no need to sort on node, it happens implicitly.
130
James Kuszmaulca818162024-07-21 20:56:49 -0700131 if (sort_on == "name") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600132 // Sort on name using std::string_view::operator< for lexicographic order.
133 std::sort(sorted_statuses.begin(), sorted_statuses.end(),
134 [](const aos::starter::ApplicationStatus *lhs,
135 const aos::starter::ApplicationStatus *rhs) {
136 return lhs->name()->string_view() < rhs->name()->string_view();
137 });
James Kuszmaulca818162024-07-21 20:56:49 -0700138 } else if (sort_on == "state") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600139 // Sort on state first, and then name for apps in same state.
140 // ApplicationStatus::state is an enum, so need to call EnumNameState()
141 // convenience wrapper to convert enum to char*, and then wrap in
142 // std::string_view for lexicographic ordering.
143 std::sort(sorted_statuses.begin(), sorted_statuses.end(),
144 [](const aos::starter::ApplicationStatus *lhs,
145 const aos::starter::ApplicationStatus *rhs) {
146 return (lhs->state() != rhs->state())
147 ? (std::string_view(
148 aos::starter::EnumNameState(lhs->state())) <
149 std::string_view(
150 aos::starter::EnumNameState(rhs->state())))
151 : (lhs->name()->string_view() <
152 rhs->name()->string_view());
153 });
James Kuszmaulca818162024-07-21 20:56:49 -0700154 } else if (sort_on == "pid") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600155 // Sort on pid first, and then name for when both apps are not running.
156 // If the app state is STOPPED, then it will not have a pid, so need to test
157 // that first. If only one app is STOPPED, then return Boolean state to put
158 // running apps before stopped.
159 std::sort(sorted_statuses.begin(), sorted_statuses.end(),
160 [](const aos::starter::ApplicationStatus *lhs,
161 const aos::starter::ApplicationStatus *rhs) {
162 if (lhs->state() == aos::starter::State::STOPPED) {
163 if (rhs->state() == aos::starter::State::STOPPED) {
164 return lhs->name()->string_view() <
165 rhs->name()->string_view();
166 } else {
167 return false;
168 }
169 } else {
170 if (rhs->state() == aos::starter::State::STOPPED) {
171 return true;
172 } else {
173 return lhs->pid() < rhs->pid();
174 }
175 }
176 });
James Kuszmaulca818162024-07-21 20:56:49 -0700177 } else if (sort_on == "uptime") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600178 // Sort on last_start_time first, and then name for when both apps are not
179 // running, or have exact same start time. Only use last_start_time when app
180 // is not STOPPED. If only one app is STOPPED, then return Boolean state to
181 // put running apps before stopped.
182 std::sort(
183 sorted_statuses.begin(), sorted_statuses.end(),
184 [](const aos::starter::ApplicationStatus *lhs,
185 const aos::starter::ApplicationStatus *rhs) {
186 if (lhs->state() == aos::starter::State::STOPPED) {
187 if (rhs->state() == aos::starter::State::STOPPED) {
188 return lhs->name()->string_view() < rhs->name()->string_view();
189 } else {
190 return false;
191 }
192 } else {
193 if (rhs->state() == aos::starter::State::STOPPED) {
194 return true;
195 } else {
196 return (lhs->last_start_time() == rhs->last_start_time())
197 ? (lhs->name()->string_view() <
198 rhs->name()->string_view())
199 : (lhs->last_start_time() < rhs->last_start_time());
200 }
201 }
202 });
203 } else {
James Kuszmaulca818162024-07-21 20:56:49 -0700204 std::cerr << "Unknown sort criteria \"" << sort_on << "\"" << std::endl;
Brian J Griglak6605beb2022-05-23 17:31:57 -0600205 exit(1);
206 }
207
208 return sorted_statuses;
209}
210
milind upadhyaya87957a2021-03-06 20:46:30 -0800211void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
James Kuszmaul293b2172021-11-10 16:20:48 -0800212 const aos::monotonic_clock::time_point &time,
213 const aos::Node *node) {
milind upadhyay4272f382021-04-07 18:03:08 -0700214 const auto last_start_time = aos::monotonic_clock::time_point(
215 chrono::nanoseconds(app_status->last_start_time()));
milind upadhyaya87957a2021-03-06 20:46:30 -0800216 const auto time_running =
217 chrono::duration_cast<chrono::seconds>(time - last_start_time);
James Kuszmaulce3497e2023-02-26 12:40:26 -0800218 const std::string last_exit_code =
219 app_status->has_last_exit_code()
220 ? std::to_string(app_status->last_exit_code())
221 : "-";
Austin Schuhf4334002021-10-16 14:19:51 -0700222 if (app_status->state() == aos::starter::State::STOPPED) {
James Kuszmaulce3497e2023-02-26 12:40:26 -0800223 absl::PrintF("%-30s %-10s %-8s %-6s %-9s %-13s\n",
James Kuszmaul293b2172021-11-10 16:20:48 -0800224 app_status->name()->string_view(),
225 (node == nullptr) ? "none" : node->name()->string_view(),
James Kuszmaulce3497e2023-02-26 12:40:26 -0800226 aos::starter::EnumNameState(app_status->state()), "", "",
227 last_exit_code);
228 } else {
229 absl::PrintF(
230 "%-30s %-10s %-8s %-6d %-9s %-13s\n", app_status->name()->string_view(),
231 (node == nullptr) ? "none" : node->name()->string_view(),
232 aos::starter::EnumNameState(app_status->state()), app_status->pid(),
233 std::to_string(time_running.count()) + 's', last_exit_code);
Austin Schuhf4334002021-10-16 14:19:51 -0700234 }
milind upadhyaya87957a2021-03-06 20:46:30 -0800235}
236
Austin Schuh43fceaf2021-10-16 14:20:22 -0700237// Prints the status for all applications.
238void GetAllStarterStatus(const aos::Configuration *config) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800239 PrintKey();
240 std::vector<const aos::Node *> missing_nodes;
241 for (const aos::Node *node : InteractNodes(config)) {
242 // Print status for all processes.
243 const auto optional_status = aos::starter::GetStarterStatus(config, node);
244 if (optional_status) {
245 const aos::FlatbufferVector<aos::starter::Status> &status =
246 optional_status->second;
247 const aos::monotonic_clock::time_point time = optional_status->first;
Brian J Griglak6605beb2022-05-23 17:31:57 -0600248 const auto &sorted_statuses = SortApplications(status);
James Kuszmaul293b2172021-11-10 16:20:48 -0800249 for (const aos::starter::ApplicationStatus *app_status :
Brian J Griglak6605beb2022-05-23 17:31:57 -0600250 sorted_statuses) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800251 PrintApplicationStatus(app_status, time, node);
252 }
253 } else {
254 missing_nodes.push_back(node);
Philipp Schrader08537492021-01-23 16:17:55 -0800255 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800256 }
257 for (const aos::Node *node : missing_nodes) {
258 if (node == nullptr) {
259 LOG(WARNING) << "No status found.";
260 } else {
261 LOG(WARNING) << "No status found for node "
262 << node->name()->string_view();
263 }
milind-u08dab882021-10-17 16:24:08 -0700264 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700265}
266
267// Handles the "status" command. Returns true if the help message should be
268// printed.
269bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
270 if (argc == 1) {
271 GetAllStarterStatus(config);
Philipp Schrader08537492021-01-23 16:17:55 -0800272 } else if (argc == 2) {
273 // Print status for the specified process.
milind upadhyay4272f382021-04-07 18:03:08 -0700274 const auto application_name =
275 aos::starter::FindApplication(argv[1], config);
Austin Schuh43fceaf2021-10-16 14:20:22 -0700276 if (application_name == "all") {
277 GetAllStarterStatus(config);
278 return false;
279 }
280
James Kuszmaul293b2172021-11-10 16:20:48 -0800281 const std::vector<const aos::Node *> application_nodes =
282 InteractNodesForApplication(config, application_name);
283 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700284 return false;
285 }
milind upadhyaya87957a2021-03-06 20:46:30 -0800286 PrintKey();
James Kuszmaul293b2172021-11-10 16:20:48 -0800287 for (const aos::Node *node : application_nodes) {
James Kuszmaule4bb0a22022-01-07 18:14:43 -0800288 auto optional_status =
289 aos::starter::GetStatus(application_name, config, node);
290 if (optional_status.has_value()) {
James Kuszmaul2ca441b2022-01-07 18:16:23 -0800291 PrintApplicationStatus(&optional_status.value().second.message(),
292 optional_status.value().first, node);
James Kuszmaule4bb0a22022-01-07 18:14:43 -0800293 } else {
294 if (node != nullptr) {
295 LOG(ERROR) << "No status available yet for \"" << application_name
296 << "\" on node \"" << node->name()->string_view() << "\".";
297 } else {
298 LOG(ERROR) << "No status available yet for \"" << application_name
299 << "\".";
300 }
301 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800302 }
Philipp Schrader08537492021-01-23 16:17:55 -0800303 } else {
304 LOG(ERROR) << "The \"status\" command requires zero or one arguments.";
305 return true;
306 }
307 return false;
308}
Tyler Chatowa79419d2020-08-12 20:12:11 -0700309
Austin Schuh43fceaf2021-10-16 14:20:22 -0700310// Sends the provided command to all applications. Prints the success text on
311// success, and failure text on failure.
312void InteractWithAll(const aos::Configuration *config,
313 const aos::starter::Command command,
314 std::string_view success_text,
315 std::string_view failure_text) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800316 std::map<const aos::Node *,
317 std::unique_ptr<aos::FlatbufferVector<aos::starter::Status>>>
318 statuses;
319
320 for (const aos::Node *node : InteractNodes(config)) {
321 std::optional<std::pair<aos::monotonic_clock::time_point,
322 const aos::FlatbufferVector<aos::starter::Status>>>
323 optional_status = aos::starter::GetStarterStatus(config, node);
324 if (optional_status.has_value()) {
325 statuses[node] =
326 std::make_unique<aos::FlatbufferVector<aos::starter::Status>>(
327 optional_status.value().second);
328 } else {
329 if (node == nullptr) {
330 LOG(WARNING) << "Starter not running";
331 } else {
332 LOG(WARNING) << "Starter not running on node "
333 << node->name()->string_view();
334 }
335 }
336 }
337
338 if (!statuses.empty()) {
339 std::vector<aos::starter::ApplicationCommand> commands;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700340
341 for (const aos::Application *application : *config->applications()) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800342 const std::string_view application_name =
343 application->name()->string_view();
344 const std::vector<const aos::Node *> application_nodes =
345 InteractNodesForApplication(config, application_name);
346 // Ignore any applications which aren't supposed to be started.
347 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700348 continue;
349 }
350
James Kuszmaul293b2172021-11-10 16:20:48 -0800351 std::vector<const aos::Node *> running_nodes;
352 if (application->autostart()) {
353 running_nodes = application_nodes;
354 } else {
355 for (const aos::Node *node : application_nodes) {
356 const aos::starter::ApplicationStatus *application_status =
357 aos::starter::FindApplicationStatus(statuses[node]->message(),
358 application_name);
359 if (application_status->state() == aos::starter::State::STOPPED) {
360 if (node == nullptr) {
361 std::cout << "Skipping " << application_name
362 << " because it is STOPPED\n";
363 } else {
364 std::cout << "Skipping " << application_name << " on "
365 << node->name()->string_view()
366 << " because it is STOPPED\n";
367 }
368 continue;
369 } else {
370 running_nodes.push_back(node);
371 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700372 }
373 }
374
James Kuszmaul293b2172021-11-10 16:20:48 -0800375 if (!running_nodes.empty()) {
376 commands.emplace_back(aos::starter::ApplicationCommand{
377 command, application_name, running_nodes});
378 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700379 }
380
381 // Restart each running process
382 if (aos::starter::SendCommandBlocking(commands, config,
383 chrono::seconds(5))) {
384 std::cout << success_text << "all \n";
385 } else {
386 std::cout << failure_text << "all \n";
387 }
388 } else {
James Kuszmaul293b2172021-11-10 16:20:48 -0800389 LOG(WARNING) << "None of the starters we care about are running.";
Austin Schuh43fceaf2021-10-16 14:20:22 -0700390 }
391}
392
393// Handles the "start", "stop", and "restart" commands. Returns true if the
394// help message should be printed.
Philipp Schrader08537492021-01-23 16:17:55 -0800395bool InteractWithProgram(int argc, char **argv,
396 const aos::Configuration *config) {
397 const char *command_string = argv[0];
Philipp Schrader08537492021-01-23 16:17:55 -0800398 if (argc != 2) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700399 LOG(ERROR)
400 << "The \"" << command_string
401 << "\" command requires an application name or 'all' as an argument.";
Philipp Schrader08537492021-01-23 16:17:55 -0800402 return true;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700403 }
404
Philipp Schrader08537492021-01-23 16:17:55 -0800405 const auto command_search = kCommandConversions.find(command_string);
406 CHECK(command_search != kCommandConversions.end())
407 << "Internal error: \"" << command_string
408 << "\" is not in kCommandConversions.";
Philipp Schrader08537492021-01-23 16:17:55 -0800409 const aos::starter::Command command = command_search->second;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700410
411 std::string_view success_text;
412 const std::string failure_text =
413 std::string("Failed to ") + std::string(command_string) + " ";
414 switch (command) {
415 case aos::starter::Command::START:
416 success_text = "Successfully started ";
417 break;
418 case aos::starter::Command::STOP:
419 success_text = "Successfully stopped ";
420 break;
421 case aos::starter::Command::RESTART:
422 success_text = "Successfully restarted ";
423 break;
424 }
425
426 const std::string_view application_name =
427 aos::starter::FindApplication(argv[1], config);
428 if (application_name == "all") {
429 InteractWithAll(config, command, success_text, failure_text);
430 return false;
431 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800432
433 const std::vector<const aos::Node *> application_nodes =
434 InteractNodesForApplication(config, application_name);
435 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700436 return false;
437 }
438
Philipp Schrader08537492021-01-23 16:17:55 -0800439 if (aos::starter::SendCommandBlocking(command, application_name, config,
James Kuszmaul293b2172021-11-10 16:20:48 -0800440 chrono::seconds(5),
441 application_nodes)) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700442 std::cout << success_text << application_name << '\n';
Tyler Chatowa79419d2020-08-12 20:12:11 -0700443 } else {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700444 std::cout << failure_text << application_name << '\n';
Jacob Ismael6388db92021-06-28 22:51:24 -0700445 }
446 return false;
447}
448
Austin Schuh33cc4162021-10-16 14:20:28 -0700449bool Help(int /*argc*/, char ** /*argv*/,
450 const aos::Configuration * /*config*/);
451
Philipp Schrader08537492021-01-23 16:17:55 -0800452// This is the set of subcommands we support. Each subcommand accepts argc and
453// argv from its own point of view. So argv[0] is always the name of the
454// subcommand. argv[1] and up are the arguments to the subcommand.
455// The subcommand returns true if there was an error parsing the command line
456// arguments. It returns false when the command line arguments are parsed
457// successfully.
Austin Schuh33cc4162021-10-16 14:20:28 -0700458static const std::vector<
459 std::tuple<std::string,
460 std::function<bool(int argc, char **argv,
461 const aos::Configuration *config)>,
462 std::string_view>>
463 kCommands{
464 {"help", Help, ""},
465 {"status", GetStarterStatus,
466 " [application], Returns the status of the provided application, "
467 "or all applications by default"},
468 {"start", InteractWithProgram,
469 " application, Starts the provided application, "
470 "or all applications if all is provided"},
471 {"stop", InteractWithProgram,
472 " application, Stops the provided application, "
473 "or all applications if all is provided"},
474 {"restart", InteractWithProgram,
475 " application, Restarts the provided application, "
476 "or all applications if all is provided"}};
477
478bool Help(int /*argc*/, char ** /*argv*/,
479 const aos::Configuration * /*config*/) {
480 std::cout << "Valid commands are:" << std::endl;
481 for (auto entry : kCommands) {
482 std::cout << " - " << std::get<0>(entry) << std::get<2>(entry) << std::endl;
483 }
484 return false;
485}
Philipp Schrader08537492021-01-23 16:17:55 -0800486
milind-u08dab882021-10-17 16:24:08 -0700487void Autocomplete(int argc, char **argv, const aos::Configuration *config) {
488 const std::string_view command = (argc >= 2 ? argv[1] : "");
489 const std::string_view app_name = (argc >= 3 ? argv[2] : "");
490
491 std::cout << "COMPREPLY=(";
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700492 if (absl::GetFlag(FLAGS__bash_autocomplete_word) == command) {
milind-u08dab882021-10-17 16:24:08 -0700493 // Autocomplete the starter command
494 for (const auto &entry : kCommands) {
495 if (std::get<0>(entry).find(command) == 0) {
496 std::cout << '\'' << std::get<0>(entry) << "' ";
497 }
498 }
499 } else {
500 // Autocomplete the app name
501 for (const auto *app : *config->applications()) {
502 if (app->has_name() && app->name()->string_view().find(app_name) == 0) {
503 std::cout << '\'' << app->name()->string_view() << "' ";
504 }
505 }
506
507 // Autocomplete with "all"
milind-u95296dd2021-10-19 07:42:17 -0700508 if (std::string_view("all").find(app_name) == 0) {
milind-u08dab882021-10-17 16:24:08 -0700509 std::cout << "'all'";
510 }
511 }
512 std::cout << ')';
513}
514
Philipp Schrader08537492021-01-23 16:17:55 -0800515} // namespace
516
517int main(int argc, char **argv) {
518 aos::InitGoogle(&argc, &argv);
519
520 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700521 aos::configuration::ReadConfig(absl::GetFlag(FLAGS_config));
Philipp Schrader08537492021-01-23 16:17:55 -0800522
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700523 if (absl::GetFlag(FLAGS__bash_autocomplete)) {
milind-u08dab882021-10-17 16:24:08 -0700524 Autocomplete(argc, argv, &config.message());
525 return 0;
526 }
527
Philipp Schrader08537492021-01-23 16:17:55 -0800528 bool parsing_failed = false;
529
530 if (argc < 2) {
531 parsing_failed = true;
532 } else {
533 const char *command = argv[1];
Austin Schuh33cc4162021-10-16 14:20:28 -0700534 auto it = std::find_if(
535 kCommands.begin(), kCommands.end(),
536 [command](const std::tuple<
537 std::string,
538 std::function<bool(int argc, char **argv,
539 const aos::Configuration *config)>,
540 std::string_view> &t) { return std::get<0>(t) == command; });
541
Philipp Schrader08537492021-01-23 16:17:55 -0800542 if (it == kCommands.end()) {
543 parsing_failed = true;
544 } else {
Austin Schuh33cc4162021-10-16 14:20:28 -0700545 parsing_failed = std::get<1>(*it)(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800546 }
547 }
548
549 if (parsing_failed) {
Austin Schuh33cc4162021-10-16 14:20:28 -0700550 Help(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800551 return 1;
552 }
553
554 return 0;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700555}