blob: c4d324d81a6eebf882ad410d1165c6ea8bee9acb [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.
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700125 std::transform(absl::GetFlag(FLAGS_sort).begin(),
126 absl::GetFlag(FLAGS_sort).end(),
127 absl::GetFlag(FLAGS_sort).begin(), tolower);
Brian J Griglak6605beb2022-05-23 17:31:57 -0600128
129 // This function is called once for each node being reported upon, so there is
130 // no need to sort on node, it happens implicitly.
131
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700132 if (absl::GetFlag(FLAGS_sort) == "name") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600133 // Sort on name using std::string_view::operator< for lexicographic order.
134 std::sort(sorted_statuses.begin(), sorted_statuses.end(),
135 [](const aos::starter::ApplicationStatus *lhs,
136 const aos::starter::ApplicationStatus *rhs) {
137 return lhs->name()->string_view() < rhs->name()->string_view();
138 });
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700139 } else if (absl::GetFlag(FLAGS_sort) == "state") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600140 // Sort on state first, and then name for apps in same state.
141 // ApplicationStatus::state is an enum, so need to call EnumNameState()
142 // convenience wrapper to convert enum to char*, and then wrap in
143 // std::string_view for lexicographic ordering.
144 std::sort(sorted_statuses.begin(), sorted_statuses.end(),
145 [](const aos::starter::ApplicationStatus *lhs,
146 const aos::starter::ApplicationStatus *rhs) {
147 return (lhs->state() != rhs->state())
148 ? (std::string_view(
149 aos::starter::EnumNameState(lhs->state())) <
150 std::string_view(
151 aos::starter::EnumNameState(rhs->state())))
152 : (lhs->name()->string_view() <
153 rhs->name()->string_view());
154 });
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700155 } else if (absl::GetFlag(FLAGS_sort) == "pid") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600156 // Sort on pid first, and then name for when both apps are not running.
157 // If the app state is STOPPED, then it will not have a pid, so need to test
158 // that first. If only one app is STOPPED, then return Boolean state to put
159 // running apps before stopped.
160 std::sort(sorted_statuses.begin(), sorted_statuses.end(),
161 [](const aos::starter::ApplicationStatus *lhs,
162 const aos::starter::ApplicationStatus *rhs) {
163 if (lhs->state() == aos::starter::State::STOPPED) {
164 if (rhs->state() == aos::starter::State::STOPPED) {
165 return lhs->name()->string_view() <
166 rhs->name()->string_view();
167 } else {
168 return false;
169 }
170 } else {
171 if (rhs->state() == aos::starter::State::STOPPED) {
172 return true;
173 } else {
174 return lhs->pid() < rhs->pid();
175 }
176 }
177 });
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700178 } else if (absl::GetFlag(FLAGS_sort) == "uptime") {
Brian J Griglak6605beb2022-05-23 17:31:57 -0600179 // Sort on last_start_time first, and then name for when both apps are not
180 // running, or have exact same start time. Only use last_start_time when app
181 // is not STOPPED. If only one app is STOPPED, then return Boolean state to
182 // put running apps before stopped.
183 std::sort(
184 sorted_statuses.begin(), sorted_statuses.end(),
185 [](const aos::starter::ApplicationStatus *lhs,
186 const aos::starter::ApplicationStatus *rhs) {
187 if (lhs->state() == aos::starter::State::STOPPED) {
188 if (rhs->state() == aos::starter::State::STOPPED) {
189 return lhs->name()->string_view() < rhs->name()->string_view();
190 } else {
191 return false;
192 }
193 } else {
194 if (rhs->state() == aos::starter::State::STOPPED) {
195 return true;
196 } else {
197 return (lhs->last_start_time() == rhs->last_start_time())
198 ? (lhs->name()->string_view() <
199 rhs->name()->string_view())
200 : (lhs->last_start_time() < rhs->last_start_time());
201 }
202 }
203 });
204 } else {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700205 std::cerr << "Unknown sort criteria \"" << absl::GetFlag(FLAGS_sort) << "\""
206 << std::endl;
Brian J Griglak6605beb2022-05-23 17:31:57 -0600207 exit(1);
208 }
209
210 return sorted_statuses;
211}
212
milind upadhyaya87957a2021-03-06 20:46:30 -0800213void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
James Kuszmaul293b2172021-11-10 16:20:48 -0800214 const aos::monotonic_clock::time_point &time,
215 const aos::Node *node) {
milind upadhyay4272f382021-04-07 18:03:08 -0700216 const auto last_start_time = aos::monotonic_clock::time_point(
217 chrono::nanoseconds(app_status->last_start_time()));
milind upadhyaya87957a2021-03-06 20:46:30 -0800218 const auto time_running =
219 chrono::duration_cast<chrono::seconds>(time - last_start_time);
James Kuszmaulce3497e2023-02-26 12:40:26 -0800220 const std::string last_exit_code =
221 app_status->has_last_exit_code()
222 ? std::to_string(app_status->last_exit_code())
223 : "-";
Austin Schuhf4334002021-10-16 14:19:51 -0700224 if (app_status->state() == aos::starter::State::STOPPED) {
James Kuszmaulce3497e2023-02-26 12:40:26 -0800225 absl::PrintF("%-30s %-10s %-8s %-6s %-9s %-13s\n",
James Kuszmaul293b2172021-11-10 16:20:48 -0800226 app_status->name()->string_view(),
227 (node == nullptr) ? "none" : node->name()->string_view(),
James Kuszmaulce3497e2023-02-26 12:40:26 -0800228 aos::starter::EnumNameState(app_status->state()), "", "",
229 last_exit_code);
230 } else {
231 absl::PrintF(
232 "%-30s %-10s %-8s %-6d %-9s %-13s\n", app_status->name()->string_view(),
233 (node == nullptr) ? "none" : node->name()->string_view(),
234 aos::starter::EnumNameState(app_status->state()), app_status->pid(),
235 std::to_string(time_running.count()) + 's', last_exit_code);
Austin Schuhf4334002021-10-16 14:19:51 -0700236 }
milind upadhyaya87957a2021-03-06 20:46:30 -0800237}
238
Austin Schuh43fceaf2021-10-16 14:20:22 -0700239// Prints the status for all applications.
240void GetAllStarterStatus(const aos::Configuration *config) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800241 PrintKey();
242 std::vector<const aos::Node *> missing_nodes;
243 for (const aos::Node *node : InteractNodes(config)) {
244 // Print status for all processes.
245 const auto optional_status = aos::starter::GetStarterStatus(config, node);
246 if (optional_status) {
247 const aos::FlatbufferVector<aos::starter::Status> &status =
248 optional_status->second;
249 const aos::monotonic_clock::time_point time = optional_status->first;
Brian J Griglak6605beb2022-05-23 17:31:57 -0600250 const auto &sorted_statuses = SortApplications(status);
James Kuszmaul293b2172021-11-10 16:20:48 -0800251 for (const aos::starter::ApplicationStatus *app_status :
Brian J Griglak6605beb2022-05-23 17:31:57 -0600252 sorted_statuses) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800253 PrintApplicationStatus(app_status, time, node);
254 }
255 } else {
256 missing_nodes.push_back(node);
Philipp Schrader08537492021-01-23 16:17:55 -0800257 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800258 }
259 for (const aos::Node *node : missing_nodes) {
260 if (node == nullptr) {
261 LOG(WARNING) << "No status found.";
262 } else {
263 LOG(WARNING) << "No status found for node "
264 << node->name()->string_view();
265 }
milind-u08dab882021-10-17 16:24:08 -0700266 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700267}
268
269// Handles the "status" command. Returns true if the help message should be
270// printed.
271bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
272 if (argc == 1) {
273 GetAllStarterStatus(config);
Philipp Schrader08537492021-01-23 16:17:55 -0800274 } else if (argc == 2) {
275 // Print status for the specified process.
milind upadhyay4272f382021-04-07 18:03:08 -0700276 const auto application_name =
277 aos::starter::FindApplication(argv[1], config);
Austin Schuh43fceaf2021-10-16 14:20:22 -0700278 if (application_name == "all") {
279 GetAllStarterStatus(config);
280 return false;
281 }
282
James Kuszmaul293b2172021-11-10 16:20:48 -0800283 const std::vector<const aos::Node *> application_nodes =
284 InteractNodesForApplication(config, application_name);
285 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700286 return false;
287 }
milind upadhyaya87957a2021-03-06 20:46:30 -0800288 PrintKey();
James Kuszmaul293b2172021-11-10 16:20:48 -0800289 for (const aos::Node *node : application_nodes) {
James Kuszmaule4bb0a22022-01-07 18:14:43 -0800290 auto optional_status =
291 aos::starter::GetStatus(application_name, config, node);
292 if (optional_status.has_value()) {
James Kuszmaul2ca441b2022-01-07 18:16:23 -0800293 PrintApplicationStatus(&optional_status.value().second.message(),
294 optional_status.value().first, node);
James Kuszmaule4bb0a22022-01-07 18:14:43 -0800295 } else {
296 if (node != nullptr) {
297 LOG(ERROR) << "No status available yet for \"" << application_name
298 << "\" on node \"" << node->name()->string_view() << "\".";
299 } else {
300 LOG(ERROR) << "No status available yet for \"" << application_name
301 << "\".";
302 }
303 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800304 }
Philipp Schrader08537492021-01-23 16:17:55 -0800305 } else {
306 LOG(ERROR) << "The \"status\" command requires zero or one arguments.";
307 return true;
308 }
309 return false;
310}
Tyler Chatowa79419d2020-08-12 20:12:11 -0700311
Austin Schuh43fceaf2021-10-16 14:20:22 -0700312// Sends the provided command to all applications. Prints the success text on
313// success, and failure text on failure.
314void InteractWithAll(const aos::Configuration *config,
315 const aos::starter::Command command,
316 std::string_view success_text,
317 std::string_view failure_text) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800318 std::map<const aos::Node *,
319 std::unique_ptr<aos::FlatbufferVector<aos::starter::Status>>>
320 statuses;
321
322 for (const aos::Node *node : InteractNodes(config)) {
323 std::optional<std::pair<aos::monotonic_clock::time_point,
324 const aos::FlatbufferVector<aos::starter::Status>>>
325 optional_status = aos::starter::GetStarterStatus(config, node);
326 if (optional_status.has_value()) {
327 statuses[node] =
328 std::make_unique<aos::FlatbufferVector<aos::starter::Status>>(
329 optional_status.value().second);
330 } else {
331 if (node == nullptr) {
332 LOG(WARNING) << "Starter not running";
333 } else {
334 LOG(WARNING) << "Starter not running on node "
335 << node->name()->string_view();
336 }
337 }
338 }
339
340 if (!statuses.empty()) {
341 std::vector<aos::starter::ApplicationCommand> commands;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700342
343 for (const aos::Application *application : *config->applications()) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800344 const std::string_view application_name =
345 application->name()->string_view();
346 const std::vector<const aos::Node *> application_nodes =
347 InteractNodesForApplication(config, application_name);
348 // Ignore any applications which aren't supposed to be started.
349 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700350 continue;
351 }
352
James Kuszmaul293b2172021-11-10 16:20:48 -0800353 std::vector<const aos::Node *> running_nodes;
354 if (application->autostart()) {
355 running_nodes = application_nodes;
356 } else {
357 for (const aos::Node *node : application_nodes) {
358 const aos::starter::ApplicationStatus *application_status =
359 aos::starter::FindApplicationStatus(statuses[node]->message(),
360 application_name);
361 if (application_status->state() == aos::starter::State::STOPPED) {
362 if (node == nullptr) {
363 std::cout << "Skipping " << application_name
364 << " because it is STOPPED\n";
365 } else {
366 std::cout << "Skipping " << application_name << " on "
367 << node->name()->string_view()
368 << " because it is STOPPED\n";
369 }
370 continue;
371 } else {
372 running_nodes.push_back(node);
373 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700374 }
375 }
376
James Kuszmaul293b2172021-11-10 16:20:48 -0800377 if (!running_nodes.empty()) {
378 commands.emplace_back(aos::starter::ApplicationCommand{
379 command, application_name, running_nodes});
380 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700381 }
382
383 // Restart each running process
384 if (aos::starter::SendCommandBlocking(commands, config,
385 chrono::seconds(5))) {
386 std::cout << success_text << "all \n";
387 } else {
388 std::cout << failure_text << "all \n";
389 }
390 } else {
James Kuszmaul293b2172021-11-10 16:20:48 -0800391 LOG(WARNING) << "None of the starters we care about are running.";
Austin Schuh43fceaf2021-10-16 14:20:22 -0700392 }
393}
394
395// Handles the "start", "stop", and "restart" commands. Returns true if the
396// help message should be printed.
Philipp Schrader08537492021-01-23 16:17:55 -0800397bool InteractWithProgram(int argc, char **argv,
398 const aos::Configuration *config) {
399 const char *command_string = argv[0];
Philipp Schrader08537492021-01-23 16:17:55 -0800400 if (argc != 2) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700401 LOG(ERROR)
402 << "The \"" << command_string
403 << "\" command requires an application name or 'all' as an argument.";
Philipp Schrader08537492021-01-23 16:17:55 -0800404 return true;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700405 }
406
Philipp Schrader08537492021-01-23 16:17:55 -0800407 const auto command_search = kCommandConversions.find(command_string);
408 CHECK(command_search != kCommandConversions.end())
409 << "Internal error: \"" << command_string
410 << "\" is not in kCommandConversions.";
Philipp Schrader08537492021-01-23 16:17:55 -0800411 const aos::starter::Command command = command_search->second;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700412
413 std::string_view success_text;
414 const std::string failure_text =
415 std::string("Failed to ") + std::string(command_string) + " ";
416 switch (command) {
417 case aos::starter::Command::START:
418 success_text = "Successfully started ";
419 break;
420 case aos::starter::Command::STOP:
421 success_text = "Successfully stopped ";
422 break;
423 case aos::starter::Command::RESTART:
424 success_text = "Successfully restarted ";
425 break;
426 }
427
428 const std::string_view application_name =
429 aos::starter::FindApplication(argv[1], config);
430 if (application_name == "all") {
431 InteractWithAll(config, command, success_text, failure_text);
432 return false;
433 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800434
435 const std::vector<const aos::Node *> application_nodes =
436 InteractNodesForApplication(config, application_name);
437 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700438 return false;
439 }
440
Philipp Schrader08537492021-01-23 16:17:55 -0800441 if (aos::starter::SendCommandBlocking(command, application_name, config,
James Kuszmaul293b2172021-11-10 16:20:48 -0800442 chrono::seconds(5),
443 application_nodes)) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700444 std::cout << success_text << application_name << '\n';
Tyler Chatowa79419d2020-08-12 20:12:11 -0700445 } else {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700446 std::cout << failure_text << application_name << '\n';
Jacob Ismael6388db92021-06-28 22:51:24 -0700447 }
448 return false;
449}
450
Austin Schuh33cc4162021-10-16 14:20:28 -0700451bool Help(int /*argc*/, char ** /*argv*/,
452 const aos::Configuration * /*config*/);
453
Philipp Schrader08537492021-01-23 16:17:55 -0800454// This is the set of subcommands we support. Each subcommand accepts argc and
455// argv from its own point of view. So argv[0] is always the name of the
456// subcommand. argv[1] and up are the arguments to the subcommand.
457// The subcommand returns true if there was an error parsing the command line
458// arguments. It returns false when the command line arguments are parsed
459// successfully.
Austin Schuh33cc4162021-10-16 14:20:28 -0700460static const std::vector<
461 std::tuple<std::string,
462 std::function<bool(int argc, char **argv,
463 const aos::Configuration *config)>,
464 std::string_view>>
465 kCommands{
466 {"help", Help, ""},
467 {"status", GetStarterStatus,
468 " [application], Returns the status of the provided application, "
469 "or all applications by default"},
470 {"start", InteractWithProgram,
471 " application, Starts the provided application, "
472 "or all applications if all is provided"},
473 {"stop", InteractWithProgram,
474 " application, Stops the provided application, "
475 "or all applications if all is provided"},
476 {"restart", InteractWithProgram,
477 " application, Restarts the provided application, "
478 "or all applications if all is provided"}};
479
480bool Help(int /*argc*/, char ** /*argv*/,
481 const aos::Configuration * /*config*/) {
482 std::cout << "Valid commands are:" << std::endl;
483 for (auto entry : kCommands) {
484 std::cout << " - " << std::get<0>(entry) << std::get<2>(entry) << std::endl;
485 }
486 return false;
487}
Philipp Schrader08537492021-01-23 16:17:55 -0800488
milind-u08dab882021-10-17 16:24:08 -0700489void Autocomplete(int argc, char **argv, const aos::Configuration *config) {
490 const std::string_view command = (argc >= 2 ? argv[1] : "");
491 const std::string_view app_name = (argc >= 3 ? argv[2] : "");
492
493 std::cout << "COMPREPLY=(";
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700494 if (absl::GetFlag(FLAGS__bash_autocomplete_word) == command) {
milind-u08dab882021-10-17 16:24:08 -0700495 // Autocomplete the starter command
496 for (const auto &entry : kCommands) {
497 if (std::get<0>(entry).find(command) == 0) {
498 std::cout << '\'' << std::get<0>(entry) << "' ";
499 }
500 }
501 } else {
502 // Autocomplete the app name
503 for (const auto *app : *config->applications()) {
504 if (app->has_name() && app->name()->string_view().find(app_name) == 0) {
505 std::cout << '\'' << app->name()->string_view() << "' ";
506 }
507 }
508
509 // Autocomplete with "all"
milind-u95296dd2021-10-19 07:42:17 -0700510 if (std::string_view("all").find(app_name) == 0) {
milind-u08dab882021-10-17 16:24:08 -0700511 std::cout << "'all'";
512 }
513 }
514 std::cout << ')';
515}
516
Philipp Schrader08537492021-01-23 16:17:55 -0800517} // namespace
518
519int main(int argc, char **argv) {
520 aos::InitGoogle(&argc, &argv);
521
522 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700523 aos::configuration::ReadConfig(absl::GetFlag(FLAGS_config));
Philipp Schrader08537492021-01-23 16:17:55 -0800524
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700525 if (absl::GetFlag(FLAGS__bash_autocomplete)) {
milind-u08dab882021-10-17 16:24:08 -0700526 Autocomplete(argc, argv, &config.message());
527 return 0;
528 }
529
Philipp Schrader08537492021-01-23 16:17:55 -0800530 bool parsing_failed = false;
531
532 if (argc < 2) {
533 parsing_failed = true;
534 } else {
535 const char *command = argv[1];
Austin Schuh33cc4162021-10-16 14:20:28 -0700536 auto it = std::find_if(
537 kCommands.begin(), kCommands.end(),
538 [command](const std::tuple<
539 std::string,
540 std::function<bool(int argc, char **argv,
541 const aos::Configuration *config)>,
542 std::string_view> &t) { return std::get<0>(t) == command; });
543
Philipp Schrader08537492021-01-23 16:17:55 -0800544 if (it == kCommands.end()) {
545 parsing_failed = true;
546 } else {
Austin Schuh33cc4162021-10-16 14:20:28 -0700547 parsing_failed = std::get<1>(*it)(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800548 }
549 }
550
551 if (parsing_failed) {
Austin Schuh33cc4162021-10-16 14:20:28 -0700552 Help(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800553 return 1;
554 }
555
556 return 0;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700557}