blob: ea68fff5501e16fcc6223f2268dd996030138345 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <base/at_exit.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/memory/ref_counted.h>
#include <base/message_loop/message_loop.h>
#include <base/run_loop.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_piece.h>
#include <base/strings/string_split.h>
#include <base/strings/stringprintf.h>
#include <base/sys_info.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include <chromeos/dbus/service_constants.h>
#include <crosvm/qcow_utils.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <dbus/object_path.h>
#include <dbus/object_proxy.h>
#include <vm_cicerone/proto_bindings/cicerone_service.pb.h>
using std::string;
namespace {
constexpr int kDefaultTimeoutMs = 5 * 1000;
int CreateLxdContainer(dbus::ObjectProxy* proxy,
const string& vm_name,
const string& container_name,
const string& owner_id,
string image_server,
string image_alias) {
LOG(INFO) << "Creating LXD container";
dbus::MethodCall method_call(vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kCreateLxdContainerMethod);
dbus::MessageWriter writer(&method_call);
vm_tools::cicerone::CreateLxdContainerRequest request;
request.set_vm_name(vm_name);
request.set_container_name(container_name);
request.set_owner_id(owner_id);
request.set_image_server(std::move(image_server));
request.set_image_alias(std::move(image_alias));
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode CreateLxdContainer protobuf";
return -1;
}
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDefaultTimeoutMs);
if (!dbus_response) {
LOG(ERROR) << "Failed to send dbus message to cicerone service";
return -1;
}
dbus::MessageReader reader(dbus_response.get());
vm_tools::cicerone::CreateLxdContainerResponse response;
if (!reader.PopArrayOfBytesAsProto(&response)) {
LOG(ERROR) << "Failed to parse response protobuf";
return -1;
}
vm_tools::cicerone::CreateLxdContainerResponse::Status status =
response.status();
if (status != vm_tools::cicerone::CreateLxdContainerResponse::CREATING &&
status != vm_tools::cicerone::CreateLxdContainerResponse::EXISTS) {
LOG(ERROR) << "Failed to create LXD container: "
<< response.failure_reason();
return -1;
}
if (status == vm_tools::cicerone::CreateLxdContainerResponse::EXISTS) {
LOG(INFO) << "Container " << container_name << " already existed";
return 0;
} else {
LOG(INFO) << "Creating container " << container_name
<< " in the background";
return 0;
}
}
int StartLxdContainer(dbus::ObjectProxy* proxy,
const string& vm_name,
const string& container_name,
const string& owner_id) {
LOG(INFO) << "Starting LXD container";
dbus::MethodCall method_call(vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kStartLxdContainerMethod);
dbus::MessageWriter writer(&method_call);
vm_tools::cicerone::StartLxdContainerRequest request;
request.set_vm_name(vm_name);
request.set_container_name(container_name);
request.set_owner_id(owner_id);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode StartLxdContainer protobuf";
return -1;
}
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDefaultTimeoutMs);
if (!dbus_response) {
LOG(ERROR) << "Failed to send dbus message to cicerone service";
return -1;
}
dbus::MessageReader reader(dbus_response.get());
vm_tools::cicerone::StartLxdContainerResponse response;
if (!reader.PopArrayOfBytesAsProto(&response)) {
LOG(ERROR) << "Failed to parse response protobuf";
return -1;
}
vm_tools::cicerone::StartLxdContainerResponse::Status status =
response.status();
if (status != vm_tools::cicerone::StartLxdContainerResponse::STARTED &&
status != vm_tools::cicerone::StartLxdContainerResponse::RUNNING) {
LOG(ERROR) << "Failed to start LXD container: "
<< response.failure_reason();
return -1;
}
if (status == vm_tools::cicerone::StartLxdContainerResponse::RUNNING) {
LOG(INFO) << "Container " << container_name << " already running";
return 0;
} else {
LOG(INFO) << "Started container: " << container_name;
return 0;
}
}
int GetLxdContainerUsername(dbus::ObjectProxy* proxy,
const string& vm_name,
const string& container_name,
const string& owner_id) {
LOG(INFO) << "Getting LXD container primary username";
dbus::MethodCall method_call(
vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kGetLxdContainerUsernameMethod);
dbus::MessageWriter writer(&method_call);
vm_tools::cicerone::GetLxdContainerUsernameRequest request;
request.set_vm_name(vm_name);
request.set_container_name(container_name);
request.set_owner_id(owner_id);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode GetLxdContainerUsernameRequest protobuf";
return -1;
}
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDefaultTimeoutMs);
if (!dbus_response) {
LOG(ERROR) << "Failed to send dbus message to cicerone service";
return -1;
}
dbus::MessageReader reader(dbus_response.get());
vm_tools::cicerone::GetLxdContainerUsernameResponse response;
if (!reader.PopArrayOfBytesAsProto(&response)) {
LOG(ERROR) << "Failed to parse response protobuf";
return -1;
}
vm_tools::cicerone::GetLxdContainerUsernameResponse::Status status =
response.status();
if (status != vm_tools::cicerone::GetLxdContainerUsernameResponse::SUCCESS) {
LOG(ERROR) << "Failed to get primary username: "
<< response.failure_reason();
return -1;
}
LOG(INFO) << "Container primary user is: " << response.username();
return 0;
}
int SetUpLxdContainerUser(dbus::ObjectProxy* proxy,
const string& vm_name,
const string& container_name,
const string& owner_id,
string container_username) {
LOG(INFO) << "Setting up LXD container user";
dbus::MethodCall method_call(
vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kSetUpLxdContainerUserMethod);
dbus::MessageWriter writer(&method_call);
vm_tools::cicerone::SetUpLxdContainerUserRequest request;
request.set_vm_name(vm_name);
request.set_container_name(container_name);
request.set_owner_id(owner_id);
request.set_container_username(std::move(container_username));
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode SetUpLxdContainerUser protobuf";
return -1;
}
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDefaultTimeoutMs);
if (!dbus_response) {
LOG(ERROR) << "Failed to send dbus message to cicerone service";
return -1;
}
dbus::MessageReader reader(dbus_response.get());
vm_tools::cicerone::SetUpLxdContainerUserResponse response;
if (!reader.PopArrayOfBytesAsProto(&response)) {
LOG(ERROR) << "Failed to parse response protobuf";
return -1;
}
vm_tools::cicerone::SetUpLxdContainerUserResponse::Status status =
response.status();
if (status != vm_tools::cicerone::SetUpLxdContainerUserResponse::EXISTS &&
status != vm_tools::cicerone::SetUpLxdContainerUserResponse::SUCCESS) {
LOG(ERROR) << "Failed to set up user: " << response.failure_reason();
return -1;
}
if (status == vm_tools::cicerone::SetUpLxdContainerUserResponse::EXISTS) {
LOG(INFO) << "Container user already exists";
return 0;
} else {
LOG(INFO) << "Created user in container";
return 0;
}
}
int LaunchApplication(dbus::ObjectProxy* proxy,
string owner_id,
string name,
string container_name,
string application) {
if (application.empty()) {
LOG(ERROR) << "--application is required";
return -1;
}
LOG(INFO) << "Starting application " << application << " in '" << name << ":"
<< container_name << "'";
dbus::MethodCall method_call(
vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kLaunchContainerApplicationMethod);
dbus::MessageWriter writer(&method_call);
vm_tools::cicerone::LaunchContainerApplicationRequest request;
request.set_owner_id(owner_id);
request.set_vm_name(name);
request.set_container_name(container_name);
request.set_desktop_file_id(application);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode LaunchContainerApplicationRequest protobuf";
return -1;
}
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDefaultTimeoutMs);
if (!dbus_response) {
LOG(ERROR) << "Failed to send dbus message to cicerone service";
return -1;
}
dbus::MessageReader reader(dbus_response.get());
vm_tools::cicerone::LaunchContainerApplicationResponse response;
if (!reader.PopArrayOfBytesAsProto(&response)) {
LOG(ERROR) << "Failed to parse response protobuf";
return -1;
}
if (!response.success()) {
LOG(ERROR) << "Failed to launch application: " << response.failure_reason();
return -1;
}
LOG(INFO) << "Launched application " << application << " in '" << name << ":"
<< container_name << "'";
return 0;
}
void Write(const std::string& output_filepath, const std::string& content) {
int content_size = content.size();
if (content_size != base::WriteFile(base::FilePath(output_filepath),
content.c_str(), content_size)) {
LOG(ERROR) << "Failed to write to file " << output_filepath;
}
}
int GetIcon(dbus::ObjectProxy* proxy,
string owner_id,
string name,
string container_name,
string application,
int icon_size,
int scale,
string output_filepath) {
if (application.empty()) {
LOG(ERROR) << "--application is required";
return -1;
}
if (output_filepath.empty()) {
LOG(ERROR) << "--output_filepath is required";
return -1;
}
LOG(INFO) << "Getting icon for " << application << " in '" << name << ":"
<< container_name << "'";
dbus::MethodCall method_call(vm_tools::cicerone::kVmCiceroneInterface,
vm_tools::cicerone::kGetContainerAppIconMethod);
dbus::MessageWriter writer(&method_call);
vm_tools::cicerone::ContainerAppIconRequest request;
request.set_owner_id(owner_id);
request.set_vm_name(name);
request.set_container_name(container_name);
request.add_desktop_file_ids(application);
request.set_size(icon_size);
request.set_scale(scale);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << "Failed to encode ContainerAppIconRequest protobuf";
return -1;
}
std::unique_ptr<dbus::Response> dbus_response =
proxy->CallMethodAndBlock(&method_call, kDefaultTimeoutMs);
if (!dbus_response) {
LOG(ERROR) << "Failed to send dbus message to cicerone service";
return -1;
}
dbus::MessageReader reader(dbus_response.get());
vm_tools::cicerone::ContainerAppIconResponse response;
if (!reader.PopArrayOfBytesAsProto(&response)) {
LOG(ERROR) << "Failed to parse response protobuf";
return -1;
}
// This should have up to one icon since the input has only one application
// file ID.
CHECK_LE(response.icons_size(), 1);
for (vm_tools::cicerone::DesktopIcon icon : response.icons()) {
if (!icon.icon().empty())
Write(output_filepath, icon.icon());
}
return 0;
}
} // namespace
int main(int argc, char** argv) {
base::AtExitManager at_exit;
// Operations.
DEFINE_bool(create_lxd_container, false, "Create an LXD container");
DEFINE_bool(start_lxd_container, false, "Start an LXD container");
DEFINE_bool(get_username, false, "Get the primary username in a container");
DEFINE_bool(set_up_lxd_user, false, "Set up a user in an LXD container");
DEFINE_bool(launch_application, false,
"Launches an application in a container");
DEFINE_bool(get_icon, false, "Get an app icon from a container within a VM");
// Parameters.
DEFINE_string(vm_name, "", "VM name");
DEFINE_string(container_name, "", "Container name");
DEFINE_string(owner_id, "", "User id");
DEFINE_string(image_server, "", "Image server to pull a container from");
DEFINE_string(image_alias, "", "Container image alias");
DEFINE_string(container_username, "", "Container username");
DEFINE_string(application, "", "Name of the application to launch");
DEFINE_string(output_filepath, "",
"Filename with path to write appliction icon to");
DEFINE_int32(icon_size, 48,
"The size of the icon to get is this icon_size by icon_size");
DEFINE_int32(scale, 1, "The scale that the icon is designed to use with");
brillo::FlagHelper::Init(argc, argv, "vm_cicerone client tool");
brillo::InitLog(brillo::kLogToStderrIfTty);
base::MessageLoopForIO message_loop;
dbus::Bus::Options opts;
opts.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(std::move(opts)));
if (!bus->Connect()) {
LOG(ERROR) << "Failed to connect to system bus";
return -1;
}
dbus::ObjectProxy* proxy = bus->GetObjectProxy(
vm_tools::cicerone::kVmCiceroneServiceName,
dbus::ObjectPath(vm_tools::cicerone::kVmCiceroneServicePath));
if (!proxy) {
LOG(ERROR) << "Unable to get dbus proxy for "
<< vm_tools::cicerone::kVmCiceroneServiceName;
return -1;
}
// The standard says that bool to int conversion is implicit and that
// false => 0 and true => 1.
// clang-format off
if (FLAGS_create_lxd_container + FLAGS_start_lxd_container +
FLAGS_set_up_lxd_user + FLAGS_get_username + FLAGS_launch_application +
FLAGS_get_icon != 1) {
// clang-format on
LOG(ERROR) << "Exactly one of --create_lxd_container, "
<< "--start_lxd_container, --set_up_lxd_user, "
<< "--get_username, --launch_application "
<< "or --get_icon must be provided";
return -1;
}
// Every D-Bus method for cicerone requires owner ID, VM name, and container
// name.
if (FLAGS_owner_id.empty()) {
LOG(ERROR) << "--owner_id is required";
return -1;
}
if (FLAGS_vm_name.empty()) {
LOG(ERROR) << "--vm_name is required";
return -1;
}
if (FLAGS_container_name.empty()) {
LOG(ERROR) << "--container_name is required";
return -1;
}
if (FLAGS_create_lxd_container) {
return CreateLxdContainer(proxy, FLAGS_vm_name, FLAGS_container_name,
FLAGS_owner_id, std::move(FLAGS_image_server),
std::move(FLAGS_image_alias));
} else if (FLAGS_start_lxd_container) {
return StartLxdContainer(proxy, FLAGS_vm_name, FLAGS_container_name,
FLAGS_owner_id);
} else if (FLAGS_set_up_lxd_user) {
return SetUpLxdContainerUser(proxy, FLAGS_vm_name, FLAGS_container_name,
FLAGS_owner_id,
std::move(FLAGS_container_username));
} else if (FLAGS_get_username) {
return GetLxdContainerUsername(proxy, FLAGS_vm_name, FLAGS_container_name,
FLAGS_owner_id);
} else if (FLAGS_launch_application) {
return LaunchApplication(
proxy, std::move(FLAGS_owner_id), std::move(FLAGS_vm_name),
std::move(FLAGS_container_name), std::move(FLAGS_application));
} else if (FLAGS_get_icon) {
return GetIcon(proxy, std::move(FLAGS_owner_id), std::move(FLAGS_vm_name),
std::move(FLAGS_container_name),
std::move(FLAGS_application), FLAGS_icon_size, FLAGS_scale,
std::move(FLAGS_output_filepath));
}
// Unreachable.
return 0;
}