blob: 9ccfa91c4c3c5aa42574083ec6963243325e2258 [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 <limits.h>
#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
#include <linux/vm_sockets.h> // Needs to come after sys/socket.h
#include <memory>
#include <string>
// syslog.h and base/logging.h both try to #define LOG_INFO and LOG_WARNING.
// We need to #undef at least these two before including base/logging.h. The
// others are included to be consistent.
namespace {
const int kSyslogDebug = LOG_DEBUG;
const int kSyslogInfo = LOG_INFO;
const int kSyslogWarning = LOG_WARNING;
const int kSyslogError = LOG_ERR;
const int kSyslogCritical = LOG_CRIT;
#undef LOG_INFO
#undef LOG_WARNING
#undef LOG_ERR
#undef LOG_CRIT
} // namespace
#include <base/at_exit.h>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/logging.h>
#include <base/message_loop/message_pump_type.h>
#include <base/run_loop.h>
#include <base/strings/stringprintf.h>
#include <base/synchronization/waitable_event.h>
#include <base/task/single_thread_task_executor.h>
#include <base/task_runner.h>
#include <base/threading/thread.h>
#include <vm_protos/proto_bindings/container_guest.grpc.pb.h>
#include <chromeos/constants/vm_tools.h>
#include "vm_tools/garcon/host_notifier.h"
#include "vm_tools/garcon/package_kit_proxy.h"
#include "vm_tools/garcon/service_impl.h"
constexpr char kLogPrefix[] = "garcon: ";
constexpr char kAllowAnyUserSwitch[] = "allow_any_user";
constexpr char kServerSwitch[] = "server";
constexpr char kClientSwitch[] = "client";
constexpr char kUrlSwitch[] = "url";
constexpr char kTerminalSwitch[] = "terminal";
constexpr uint32_t kVsockPortStart = 10000;
constexpr uint32_t kVsockPortEnd = 20000;
constexpr uid_t kCrostiniDefaultUid = 1000;
bool LogToSyslog(logging::LogSeverity severity,
const char* /* file */,
int /* line */,
size_t message_start,
const std::string& message) {
switch (severity) {
case logging::LOGGING_INFO:
severity = kSyslogInfo;
break;
case logging::LOGGING_WARNING:
severity = kSyslogWarning;
break;
case logging::LOGGING_ERROR:
severity = kSyslogError;
break;
case logging::LOGGING_FATAL:
severity = kSyslogCritical;
break;
default:
severity = kSyslogDebug;
break;
}
syslog(severity, "%s", message.c_str() + message_start);
return true;
}
void RunGarconService(vm_tools::garcon::PackageKitProxy* pk_proxy,
base::WaitableEvent* event,
std::shared_ptr<grpc::Server>* server_copy,
int* vsock_listen_port,
scoped_refptr<base::TaskRunner> task_runner,
vm_tools::garcon::HostNotifier* host_notifier) {
// We don't want to receive SIGTERM on this thread.
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, nullptr);
// See crbug.com/922694 for more reference.
// There's a bug in our patched version of gRPC where it uses signed integers
// for ports. VSOCK uses unsigned integers for ports. So if we let the kernel
// choose the port for us, then it can end up choosing one that has the high
// bit set and cause gRPC to assert on the negative port number. This was a
// much easier solution than patching gRPC or updating the kernel to keep the
// VSOCK ports in the signed integer range.
// The end on this for loop only exists to prevent running forever in case
// something else goes wrong.
for (*vsock_listen_port = kVsockPortStart; *vsock_listen_port < kVsockPortEnd;
++(*vsock_listen_port)) {
// Build the server.
grpc::ServerBuilder builder;
builder.AddListeningPort(
base::StringPrintf("vsock:%u:%d", VMADDR_CID_ANY, *vsock_listen_port),
grpc::InsecureServerCredentials(), nullptr);
vm_tools::garcon::ServiceImpl garcon_service(pk_proxy, task_runner.get(),
host_notifier);
builder.RegisterService(&garcon_service);
std::shared_ptr<grpc::Server> server(builder.BuildAndStart().release());
if (!server) {
LOG(WARNING) << "garcon failed binding requested vsock port "
<< *vsock_listen_port << ", trying again with a new port";
continue;
}
*server_copy = server;
event->Signal();
LOG(INFO) << "Server listening on vsock port " << *vsock_listen_port;
// The following call will return once we invoke Shutdown on the gRPC
// server when the main RunLoop exits.
server->Wait();
break;
}
}
void CreatePackageKitProxy(
base::WaitableEvent* event,
vm_tools::garcon::HostNotifier* host_notifier,
std::unique_ptr<vm_tools::garcon::PackageKitProxy>* proxy_ptr) {
// We don't want to receive SIGTERM on this thread.
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, nullptr);
*proxy_ptr = vm_tools::garcon::PackageKitProxy::Create(host_notifier);
event->Signal();
}
void PrintUsage() {
LOG(INFO) << "Garcon: VM container bridge for Chrome OS\n\n"
<< "Mode Switches (must use one):\n"
<< "Mode Switch:\n"
<< " --server: run in background as daemon\n"
<< " --client: run as client and send message to host\n"
<< "Client Switches (only with --client):\n"
<< " --url: opens all arguments as URLs in host browser\n"
<< "Server Switches (only with --server):\n"
<< " --allow_any_user: allow running as non-default uid\n";
}
int main(int argc, char** argv) {
base::AtExitManager at_exit;
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
base::FileDescriptorWatcher watcher(task_executor.task_runner());
base::CommandLine::Init(argc, argv);
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
logging::InitLogging(logging::LoggingSettings());
bool serverMode = cl->HasSwitch(kServerSwitch);
bool clientMode = cl->HasSwitch(kClientSwitch);
// The standard says that bool to int conversion is implicit and that
// false => 0 and true => 1.
// clang-format off
if (serverMode + clientMode != 1) {
// clang-format on
LOG(ERROR) << "Exactly one of --server or --client must be used.";
PrintUsage();
return -1;
}
if (clientMode) {
if (cl->HasSwitch(kUrlSwitch)) {
std::vector<std::string> args = cl->GetArgs();
if (args.empty()) {
LOG(ERROR) << "Missing URL arguments in --url mode";
PrintUsage();
return -1;
}
// All arguments are URLs, send them to the host to be opened. The host
// will do its own verification for validity of the URLs.
for (const auto& arg : args) {
if (!vm_tools::garcon::HostNotifier::OpenUrlInHost(arg)) {
return -1;
}
}
return 0;
} else if (cl->HasSwitch(kTerminalSwitch)) {
std::vector<std::string> args = cl->GetArgs();
if (vm_tools::garcon::HostNotifier::OpenTerminal(std::move(args)))
return 0;
else
return -1;
}
LOG(ERROR) << "Missing client switch for client mode.";
PrintUsage();
return -1;
}
// Set up logging to syslog for server mode.
openlog(kLogPrefix, LOG_PID, LOG_DAEMON);
logging::SetLogMessageHandler(LogToSyslog);
// Exit if not running as the container default user.
if (getuid() != kCrostiniDefaultUid && !cl->HasSwitch(kAllowAnyUserSwitch)) {
LOG(ERROR) << "garcon normally runs only as uid(" << kCrostiniDefaultUid
<< "). Use --allow_any_user to override";
return -1;
}
// Note on threading model. There are 4 threads used in garcon. One is for the
// incoming gRPC requests. One is for the D-Bus communication with the
// PackageKit daemon. The third is the main thread which is for gRPC requests
// to the host as well as for monitoring filesystem changes (which result in a
// gRPC call to the host under certain conditions). The main thing to be
// careful of is that the gRPC thread for incoming requests is never blocking
// on the gRPC thread for outgoing requests (since they are both talking to
// cicerone, and both of those operations in cicerone are likely going to use
// the same D-Bus thread for communication within cicerone). The fourth thread
// is for running tasks initiated by garcon service.
// Thread that the gRPC server is running on.
base::Thread grpc_thread{"gRPC Server Thread"};
if (!grpc_thread.Start()) {
LOG(ERROR) << "Failed starting the gRPC thread";
return -1;
}
// Thread that D-Bus communication runs on.
base::Thread dbus_thread{"D-Bus Thread"};
if (!dbus_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0))) {
LOG(ERROR) << "Failed starting the D-Bus thread";
return -1;
}
// Thread that tasks started from garcon service run on.
// Specifically, Ansible playbook application runs on
// |garcon_service_tasks_thread|.
base::Thread garcon_service_tasks_thread{"Garcon Service Tasks Thread"};
if (!garcon_service_tasks_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0))) {
LOG(ERROR) << "Failed starting the garcon service tasks thread";
return -1;
}
// Setup the HostNotifier on the run loop for the main thread. It needs to
// have its own run loop separate from the gRPC server & D-Bus server since it
// will be using base::FilePathWatcher to identify installed application and
// mime type changes.
base::RunLoop run_loop;
std::unique_ptr<vm_tools::garcon::HostNotifier> host_notifier =
vm_tools::garcon::HostNotifier::Create(run_loop.QuitClosure());
if (!host_notifier) {
LOG(ERROR) << "Failure setting up the HostNotifier";
return -1;
}
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
// This needs to be created on the D-Bus thread.
std::unique_ptr<vm_tools::garcon::PackageKitProxy> pk_proxy;
bool ret = dbus_thread.task_runner()->PostTask(
FROM_HERE, base::Bind(&CreatePackageKitProxy, &event, host_notifier.get(),
&pk_proxy));
if (!ret) {
LOG(ERROR) << "Failed to post PackageKit proxy creation to D-Bus thread";
return -1;
}
// Wait for the creation to complete.
event.Wait();
if (!pk_proxy) {
LOG(ERROR) << "Failed in creating the PackageKit proxy";
return -1;
}
event.Reset();
// Launch the gRPC server on the gRPC thread.
std::shared_ptr<grpc::Server> server_copy;
int vsock_listen_port = 0;
ret = grpc_thread.task_runner()->PostTask(
FROM_HERE,
base::Bind(&RunGarconService, pk_proxy.get(), &event, &server_copy,
&vsock_listen_port, garcon_service_tasks_thread.task_runner(),
host_notifier.get()));
if (!ret) {
LOG(ERROR) << "Failed to post server startup task to grpc thread";
return -1;
}
// Wait for the gRPC server to start.
event.Wait();
if (!server_copy) {
LOG(ERROR) << "gRPC server failed to start";
return -1;
}
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
PLOG(ERROR) << "Unable to explicitly ignore SIGCHILD";
return -1;
}
if (!host_notifier->Init(static_cast<uint32_t>(vsock_listen_port),
pk_proxy.get())) {
LOG(ERROR) << "Failed to set up host notifier";
return -1;
}
// Start the main run loop now for the HostNotifier.
run_loop.Run();
// We get here after a SIGTERM gets posted and the main run loop has exited.
// We then shutdown the gRPC server (which will terminate that thread) and
// then stop the D-Bus thread. We will be the only remaining thread at that
// point so everything can be safely destructed and we remove the need for
// any weak pointers.
server_copy->Shutdown();
dbus_thread.Stop();
garcon_service_tasks_thread.Stop();
return 0;
}