blob: fa7727a87c50e5f371296b00981f05a5b5444263 [file] [log] [blame]
//
// Copyright 2015 Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "service/ipc/ipc_handler_linux.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <base/bind.h>
#include "abstract_message_loop.h"
#include "osi/include/socket_utils/sockets.h"
#include "service/daemon.h"
#include "service/ipc/linux_ipc_host.h"
#include "service/settings.h"
namespace ipc {
IPCHandlerLinux::IPCHandlerLinux(bluetooth::Adapter* adapter,
IPCManager::Delegate* delegate)
: IPCHandler(adapter, delegate),
running_(false),
thread_("IPCHandlerLinux"),
keep_running_(true) {}
IPCHandlerLinux::~IPCHandlerLinux() {
// This will only be set if the Settings::create_ipc_socket_path() was
// originally provided.
if (!socket_path_.empty()) unlink(socket_path_.value().c_str());
}
bool IPCHandlerLinux::Run() {
CHECK(!running_);
const std::string& android_suffix =
bluetooth::Daemon::Get()->GetSettings()->android_ipc_socket_suffix();
const base::FilePath& path =
bluetooth::Daemon::Get()->GetSettings()->create_ipc_socket_path();
// Both flags cannot be set at the same time.
CHECK(android_suffix.empty() || path.empty());
if (android_suffix.empty() && path.empty()) {
LOG(ERROR) << "No domain socket path provided";
return false;
}
// An origin event loop is required.
CHECK(btbase::AbstractMessageLoop::current_task_runner());
origin_task_runner_ = btbase::AbstractMessageLoop::current_task_runner();
if (!android_suffix.empty()) {
int server_fd = osi_android_get_control_socket(android_suffix.c_str());
if (server_fd == -1) {
LOG(ERROR) << "Unable to get Android socket from: " << android_suffix;
return false;
}
LOG(INFO) << "Binding to Android server socket:" << android_suffix;
socket_.reset(server_fd);
} else {
LOG(INFO) << "Creating a Unix domain socket:" << path.value();
// TODO(armansito): This is opens the door to potentially unlinking files in
// the current directory that we're not supposed to. For now we will have an
// assumption that the daemon runs in a sandbox but we should generally do
// this properly.
unlink(path.value().c_str());
base::ScopedFD server_socket(socket(PF_UNIX, SOCK_SEQPACKET, 0));
if (!server_socket.is_valid()) {
LOG(ERROR) << "Failed to open domain socket for IPC";
return false;
}
struct sockaddr_un address;
memset(&address, 0, sizeof(address));
address.sun_family = AF_UNIX;
strncpy(address.sun_path, path.value().c_str(),
sizeof(address.sun_path) - 1);
if (bind(server_socket.get(), (struct sockaddr*)&address, sizeof(address)) <
0) {
PLOG(ERROR) << "Failed to bind IPC socket to address";
return false;
}
socket_.swap(server_socket);
socket_path_ = path;
}
CHECK(socket_.is_valid());
running_ = true; // Set this here before launching the thread.
// Start an IO thread and post the listening task.
base::Thread::Options options;
btbase::set_message_loop_type_IO(options);
if (!thread_.StartWithOptions(options)) {
LOG(ERROR) << "Failed to start IPCHandlerLinux thread";
running_ = false;
return false;
}
thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&IPCHandlerLinux::StartListeningOnThread, this));
return true;
}
void IPCHandlerLinux::Stop() {
keep_running_ = false;
// At this moment the listening thread might be blocking on the accept
// syscall. Shutdown and close the server socket before joining the thread to
// interrupt accept so that the main thread doesn't keep blocking.
shutdown(socket_.get(), SHUT_RDWR);
socket_.reset();
// Join and clean up the thread.
thread_.Stop();
// Thread exited. Notify the delegate. Post this on the event loop so that the
// callback isn't reentrant.
NotifyStoppedOnOriginThread();
}
void IPCHandlerLinux::StartListeningOnThread() {
CHECK(socket_.is_valid());
CHECK(adapter());
CHECK(running_);
LOG(INFO) << "Listening to incoming connections";
int status = listen(socket_.get(), SOMAXCONN);
if (status < 0) {
PLOG(ERROR) << "Failed to listen on domain socket";
origin_task_runner_->PostTask(
FROM_HERE, base::Bind(&IPCHandlerLinux::ShutDownOnOriginThread, this));
return;
}
NotifyStartedOnOriginThread();
// TODO(armansito): The code below can cause the daemon to run indefinitely if
// the thread is joined while it's in the middle of the EventLoop() call. The
// EventLoop() won't exit until a client terminates the connection, however
// this can be fixed by using the |thread_|'s MessageLoopForIO instead (since
// it gets stopped along with the thread).
// TODO(icoolidge): accept simultaneous clients
while (keep_running_.load()) {
int client_socket = accept4(socket_.get(), nullptr, nullptr, SOCK_NONBLOCK);
if (client_socket < 0) {
PLOG(ERROR) << "Failed to accept client connection";
continue;
}
LOG(INFO) << "Established client connection: fd=" << client_socket;
LinuxIPCHost ipc_host(client_socket, adapter());
// TODO(armansito): Use |thread_|'s MessageLoopForIO instead of using a
// custom event loop to poll from the socket.
ipc_host.EventLoop();
}
}
void IPCHandlerLinux::ShutDownOnOriginThread() {
LOG(INFO) << "Shutting down IPCHandlerLinux thread";
thread_.Stop();
running_ = false;
NotifyStoppedOnCurrentThread();
}
void IPCHandlerLinux::NotifyStartedOnOriginThread() {
if (!delegate()) return;
origin_task_runner_->PostTask(
FROM_HERE,
base::Bind(&IPCHandlerLinux::NotifyStartedOnCurrentThread, this));
}
void IPCHandlerLinux::NotifyStartedOnCurrentThread() {
if (delegate()) delegate()->OnIPCHandlerStarted(IPCManager::TYPE_LINUX);
}
void IPCHandlerLinux::NotifyStoppedOnOriginThread() {
if (!delegate()) return;
origin_task_runner_->PostTask(
FROM_HERE,
base::Bind(&IPCHandlerLinux::NotifyStoppedOnCurrentThread, this));
}
void IPCHandlerLinux::NotifyStoppedOnCurrentThread() {
if (delegate()) delegate()->OnIPCHandlerStopped(IPCManager::TYPE_LINUX);
}
} // namespace ipc