blob: 1136c54f42b8dc5cfa64c4e66bf6a62a08268497 [file] [log] [blame]
// Copyright 2017 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 <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/vm_sockets.h>
#include <memory>
#include <base/at_exit.h>
#include <base/files/scoped_file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/memory/ref_counted.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/file_utils.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <dbus/object_proxy.h>
#include <grpcpp/grpcpp.h>
#include <vm_protos/proto_bindings/vm_host.grpc.pb.h>
#include "vm_tools/syslog/forwarder.h"
namespace {
constexpr unsigned int kPort = 9999;
// Default syslogd path. When |FLAGS_log_destination| is |kDevLog| we forward
// logs using a unix domain socket.
constexpr char kDevLog[] = "/dev/log";
// Cryptohome token to be replaced in |FLAGS_log_destination|.
constexpr char kCryptohome[] = "cryptohome/";
// Cryptohome root base path.
constexpr char kCryptohomeRoot[] = "/home/root";
} // namespace
std::string GetPrimaryUserHash() {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
if (!bus->Connect()) {
PLOG(ERROR) << "Failed to connect to system D-Bus";
}
auto session_manager_proxy = bus->GetObjectProxy(
login_manager::kSessionManagerServiceName,
dbus::ObjectPath(login_manager::kSessionManagerServicePath));
dbus::MethodCall method_call(
login_manager::kSessionManagerInterface,
login_manager::kSessionManagerRetrievePrimarySession);
std::unique_ptr<dbus::Response> response =
session_manager_proxy->CallMethodAndBlock(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
if (!response.get()) {
PLOG(ERROR) << "Cannot retrieve username for primary session.";
return "";
}
dbus::MessageReader response_reader(response.get());
std::string username;
if (!response_reader.PopString(&username)) {
PLOG(ERROR) << "Primary session username bad format.";
return "";
}
std::string sanitized_username;
if (!response_reader.PopString(&sanitized_username)) {
PLOG(ERROR) << "Primary session sanitized username bad format.";
return "";
}
if (sanitized_username.empty()) {
PLOG(ERROR) << "Primary session does not exist.";
return "";
}
return sanitized_username;
}
base::FilePath ReplaceCryptohome(base::StringPiece in_path) {
if (in_path.starts_with(kCryptohome)) {
std::string user_hash = GetPrimaryUserHash();
if (!user_hash.empty()) {
in_path.remove_prefix(base::StringPiece(kCryptohome).length());
base::FilePath path =
base::FilePath(kCryptohomeRoot).Append(user_hash).Append(in_path);
// Ensure the parent dir exists.
base::FilePath parent_dir = path.DirName();
if (!base::DirectoryExists(parent_dir)) {
base::File::Error dir_error;
if (!base::CreateDirectoryAndGetError(parent_dir, &dir_error)) {
LOG(ERROR) << "Failed to create directory in /home/root: "
<< base::File::ErrorToString(dir_error);
return base::FilePath(in_path);
}
}
return path;
}
}
return base::FilePath(in_path);
}
int main(int argc, char** argv) {
base::AtExitManager at_exit;
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty);
DEFINE_string(
log_destination, kDevLog,
"Path to unix domain datagram socket to which logs will be forwarded");
brillo::FlagHelper::Init(argc, argv, "VM log forwarding tool");
bool is_socket_dest = true;
base::ScopedFD dest;
if (FLAGS_log_destination == kDevLog) {
dest.reset(socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0));
if (!dest.is_valid()) {
PLOG(ERROR) << "Failed to create unix domain datagram socket";
return EXIT_FAILURE;
}
struct sockaddr_un un = {
.sun_family = AF_UNIX,
};
if (FLAGS_log_destination.size() >= sizeof(un.sun_path)) {
LOG(ERROR) << "Requested log destination path (" << FLAGS_log_destination
<< ") is too long. Maximum path length: "
<< sizeof(un.sun_path) << " characters";
return EXIT_FAILURE;
}
// sun_path is zero-initialized above so we just need to copy the path.
memcpy(un.sun_path, FLAGS_log_destination.c_str(),
FLAGS_log_destination.size());
if (connect(dest.get(), reinterpret_cast<struct sockaddr*>(&un),
sizeof(un)) != 0) {
PLOG(ERROR) << "Failed to connect to " << FLAGS_log_destination;
return EXIT_FAILURE;
}
} else {
is_socket_dest = false;
base::FilePath dest_path = ReplaceCryptohome(FLAGS_log_destination);
dest.reset(open(dest_path.value().c_str(),
O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC, 0640));
if (!dest.is_valid()) {
PLOG(ERROR) << "Failed to open log file";
return EXIT_FAILURE;
}
LOG(INFO) << "Vm log forwarder writing to " << dest_path;
}
vm_tools::syslog::Forwarder forwarder(std::move(dest), is_socket_dest);
grpc::ServerBuilder builder;
builder.AddListeningPort(
base::StringPrintf("vsock:%u:%u", VMADDR_CID_ANY, kPort),
grpc::InsecureServerCredentials());
builder.RegisterService(&forwarder);
std::unique_ptr<grpc::Server> server = builder.BuildAndStart();
CHECK(server);
LOG(INFO) << "VM log forwarder listening on port " << kPort;
server->Wait();
return EXIT_SUCCESS;
}