// // 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 #include #include #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