summaryrefslogtreecommitdiff
path: root/service/ipc/ipc_handler_linux.cc
blob: bcd21aa7e85f7f307ffde840952b5426f10e7991 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//
//  Copyright (C) 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 "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;
  }

  CHECK(base::MessageLoop::current());  // An origin event loop is required.
  origin_task_runner_ = base::MessageLoop::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) {
      LOG(ERROR) << "Failed to bind IPC socket to address: " << strerror(errno);
      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(base::MessageLoop::TYPE_IO, 0);
  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) {
    LOG(ERROR) << "Failed to listen on domain socket: " << strerror(errno);
    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) {
      LOG(ERROR) << "Failed to accept client connection: " << strerror(errno);
      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