blob: d9d2c88ece1c16a2fc80609cf0674c923850d4a9 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "faced/faced_cli/faced_cli.h"
#include <string>
#include <vector>
#include <absl/status/status.h>
#include <absl/status/statusor.h>
#include <absl/strings/str_format.h>
#include <base/check.h>
#include <base/command_line.h>
#include <base/strings/string_piece.h>
#include <base/task/single_thread_task_executor.h>
#include <base/threading/thread.h>
#include <brillo/flag_helper.h>
#include <mojo/core/embedder/embedder.h>
#include <mojo/core/embedder/scoped_ipc_support.h>
#include "faced/faced_cli/faced_client.h"
namespace faced {
namespace {
// CLI documentation.
constexpr base::StringPiece kUsage = R"(Usage: faced_cli <command> [options]
Commands:
connect Set up a Mojo connection to Faced by bootstrapping over
Dbus and then disconnect the session.
enroll Enroll a user
--user=<string> User to enroll (eg. someone).
is-enrolled Checks whether a user is enrolled
--user=<string> User to enroll (eg. someone).
remove Unenroll a user
--user=<string> User to unenroll (eg. someone).
list List all enrolled users
clear Clear all enrollments
Full details of options can be shown using "--help".
)";
// Parse a command string into the enum type `Command`.
std::optional<Command> ParseCommand(base::StringPiece command) {
if (command == "connect") {
return Command::kConnectToFaced;
}
if (command == "enroll") {
return Command::kEnroll;
}
if (command == "is-enrolled") {
return Command::kIsEnrolled;
}
if (command == "remove") {
return Command::kRemoveEnrollment;
}
if (command == "list") {
return Command::kListEnrollments;
}
if (command == "clear") {
return Command::kClearEnrollments;
}
return std::nullopt;
}
absl::Status RunCommand(const CommandLineArgs& command) {
switch (command.command) {
case Command::kConnectToFaced:
return ConnectAndDisconnectFromFaced();
case Command::kEnroll:
return Enroll(command.user);
case Command::kIsEnrolled:
return IsEnrolled(command.user);
case Command::kRemoveEnrollment:
return RemoveEnrollment(command.user);
case Command::kListEnrollments:
return ListEnrollments();
case Command::kClearEnrollments:
return ClearEnrollments();
}
}
} // namespace
// Parse the given command line, producing a `CommandLineArgs` on success.
absl::StatusOr<CommandLineArgs> ParseCommandLine(int argc,
const char* const* argv) {
CHECK(argc > 0)
<< "Argv must contain at least one element, the program name.";
DEFINE_string(user, "", "User to enroll (eg. someone).");
if (!brillo::FlagHelper::Init(argc, argv, std::string(kUsage),
brillo::FlagHelper::InitFuncType::kReturn)) {
return absl::InvalidArgumentError("Invalid option.");
}
// Parse the sub-command.
std::vector<std::string> commands =
base::CommandLine::ForCurrentProcess()->GetArgs();
if (commands.size() != 1) {
return absl::InvalidArgumentError("Expected exactly one command.");
}
std::optional<Command> command = ParseCommand(commands[0]);
if (!command.has_value()) {
return absl::InvalidArgumentError(
absl::StrFormat("Unknown command '%s'.", commands[0]));
}
switch (command.value()) {
case Command::kConnectToFaced:
if (!FLAGS_user.empty()) {
return absl::InvalidArgumentError(
absl::StrFormat("--user argument '%s' was provided for 'connect' "
"command which does not use this argument.",
FLAGS_user));
}
break;
case Command::kEnroll:
if (FLAGS_user.empty()) {
return absl::InvalidArgumentError(
"No --user argument was provided for 'enroll' command.");
}
break;
case Command::kIsEnrolled:
if (FLAGS_user.empty()) {
return absl::InvalidArgumentError(
"No --user argument was provided for 'is-enrolled' command.");
}
break;
case Command::kRemoveEnrollment:
if (FLAGS_user.empty()) {
return absl::InvalidArgumentError(
"No --user argument was provided for 'remove' command.");
}
break;
case Command::kListEnrollments:
if (!FLAGS_user.empty()) {
return absl::InvalidArgumentError(
absl::StrFormat("--user argument '%s' was provided for 'list' "
"command which does not use this argument.",
FLAGS_user));
}
break;
case Command::kClearEnrollments:
if (!FLAGS_user.empty()) {
return absl::InvalidArgumentError(
absl::StrFormat("--user argument '%s' was provided for 'clear' "
"command which does not use this argument.",
FLAGS_user));
}
break;
}
return CommandLineArgs{
.command = *command,
.user = FLAGS_user,
};
}
int Main(int argc, char* argv[]) {
// Setup task context
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
// Basic Mojo initialization for a new process.
mojo::core::Init();
base::Thread ipc_thread("FacedCliIpc");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo::core::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
// Parse command line.
absl::StatusOr<CommandLineArgs> result = ParseCommandLine(argc, argv);
if (!result.ok()) {
std::cerr << kUsage << "\n"
<< "Error: " << result.status().message() << "\n";
return 1;
}
// Run the appropriate command.
absl::Status command_result = RunCommand(*result);
if (!command_result.ok()) {
std::cerr << "Error: " << command_result.message() << "\n";
return 1;
}
return 0;
}
} // namespace faced