1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "services/device/hid/hid_service_freebsd.h"
6
7 #if defined(OS_FREEBSD)
8 #include <dev/usb/usb_ioctl.h>
9 #else
10 #include <bus/u4b/usb_ioctl.h>
11 #endif
12
13 #include <stdint.h>
14 #include <sys/socket.h>
15 #include <sys/un.h>
16
17 #include <set>
18 #include <string>
19 #include <vector>
20
21 #include "base/bind.h"
22 #include "base/files/file_descriptor_watcher_posix.h"
23 #include "base/files/file_enumerator.h"
24 #include "base/files/file_util.h"
25 #include "base/files/file.h"
26 #include "base/location.h"
27 #include "base/logging.h"
28 #include "base/posix/eintr_wrapper.h"
29 #include "base/single_thread_task_runner.h"
30 #include "base/stl_util.h"
31 #include "base/strings/pattern.h"
32 #include "base/strings/stringprintf.h"
33 #include "base/strings/sys_string_conversions.h"
34 #include "base/strings/string_util.h"
35 #include "base/strings/string_split.h"
36 #include "base/task/post_task.h"
37 #include "base/threading/scoped_blocking_call.h"
38 #include "base/threading/thread_task_runner_handle.h"
39 #include "components/device_event_log/device_event_log.h"
40 #include "services/device/hid/hid_connection_freebsd.h"
41
42 const int kMaxPermissionChecks = 5;
43
44 namespace device {
45
46 struct HidServiceFreeBSD::ConnectParams {
ConnectParamsdevice::HidServiceFreeBSD::ConnectParams47 ConnectParams(scoped_refptr<HidDeviceInfo> device_info,
48 ConnectCallback callback)
49 : device_info(std::move(device_info)),
50 callback(std::move(callback)),
51 task_runner(base::ThreadTaskRunnerHandle::Get()),
52 blocking_task_runner(
53 base::ThreadPool::CreateSequencedTaskRunner(kBlockingTaskTraits)) {}
~ConnectParamsdevice::HidServiceFreeBSD::ConnectParams54 ~ConnectParams() {}
55
56 scoped_refptr<HidDeviceInfo> device_info;
57 ConnectCallback callback;
58 scoped_refptr<base::SequencedTaskRunner> task_runner;
59 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner;
60 base::ScopedFD fd;
61 };
62
63 class HidServiceFreeBSD::BlockingTaskRunnerHelper {
64 public:
BlockingTaskRunnerHelper(base::WeakPtr<HidServiceFreeBSD> service)65 BlockingTaskRunnerHelper(base::WeakPtr<HidServiceFreeBSD> service)
66 : service_(std::move(service)),
67 task_runner_(base::ThreadTaskRunnerHandle::Get()) {
68 DETACH_FROM_SEQUENCE(sequence_checker_);
69
70 timer_.reset(new base::RepeatingTimer());
71 devd_buffer_ = new net::IOBufferWithSize(1024);
72 }
73
~BlockingTaskRunnerHelper()74 ~BlockingTaskRunnerHelper() {
75 }
76
Start()77 void Start() {
78 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
79
80 const base::FilePath kDevRoot("/dev");
81 const std::string kUHIDPattern("/dev/uhid*");
82
83 base::FileEnumerator enumerator(kDevRoot, false, base::FileEnumerator::FILES);
84 do {
85 const base::FilePath next_device_path(enumerator.Next());
86 const std::string next_device = next_device_path.value();
87 if (next_device.empty())
88 break;
89
90 if (base::MatchPattern(next_device, kUHIDPattern))
91 OnDeviceAdded(next_device.substr(5));
92 } while (true);
93
94 SetupDevdMonitor();
95
96 task_runner_->PostTask(
97 FROM_HERE,
98 base::Bind(&HidServiceFreeBSD::FirstEnumerationComplete, service_));
99 }
100
HaveReadWritePermissions(std::string device_id)101 bool HaveReadWritePermissions(std::string device_id) {
102 std::string device_node = "/dev/" + device_id;
103 base::internal::AssertBlockingAllowed();
104
105 base::FilePath device_path(device_node);
106 base::File device_file;
107 int flags =
108 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE;
109 device_file.Initialize(device_path, flags);
110 if (!device_file.IsValid())
111 return false;
112
113 return true;
114 }
115
OnDeviceAdded(std::string device_id)116 void OnDeviceAdded(std::string device_id) {
117 base::ScopedBlockingCall scoped_blocking_call(
118 FROM_HERE, base::BlockingType::MAY_BLOCK);
119 std::string device_node = "/dev/" + device_id;
120 uint16_t vendor_id = 0xffff;
121 uint16_t product_id = 0xffff;
122 std::string product_name = "";
123 std::string serial_number = "";
124
125 std::vector<uint8_t> report_descriptor;
126
127 base::internal::AssertBlockingAllowed();
128
129 base::FilePath device_path(device_node);
130 base::File device_file;
131 int flags =
132 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE;
133 device_file.Initialize(device_path, flags);
134 if (!device_file.IsValid()) {
135 HID_LOG(ERROR) << "Failed to open '" << device_node
136 << "': "
137 << base::File::ErrorToString(device_file.error_details());
138 return;
139 }
140
141 base::ScopedFD fd;
142 fd.reset(device_file.TakePlatformFile());
143
144 struct usb_gen_descriptor ugd;
145 ugd.ugd_data = NULL;
146 ugd.ugd_maxlen = 0xffff;
147 int result = HANDLE_EINTR(
148 ioctl(fd.get(), USB_GET_REPORT_DESC, &ugd));
149
150 if (result < 0) {
151 HID_LOG(ERROR) << "Failed to get report descriptor size";
152 return;
153 }
154
155 report_descriptor.resize(ugd.ugd_actlen);
156
157 ugd.ugd_data = report_descriptor.data();
158 ugd.ugd_maxlen = ugd.ugd_actlen;
159 result = HANDLE_EINTR(
160 ioctl(fd.get(), USB_GET_REPORT_DESC, &ugd));
161
162 if (result < 0) {
163 HID_LOG(ERROR) << "Failed to get report descriptor";
164 return;
165 }
166
167 scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfo(
168 device_id,
169 /*physical_device_id*/"",
170 vendor_id,
171 product_id,
172 product_name,
173 serial_number,
174 device::mojom::HidBusType::kHIDBusTypeUSB,
175 report_descriptor,
176 device_node));
177
178 task_runner_->PostTask(FROM_HERE, base::BindOnce(&HidServiceFreeBSD::AddDevice,
179 service_, device_info));
180 }
181
OnDeviceRemoved(std::string device_id)182 void OnDeviceRemoved(std::string device_id) {
183 base::ScopedBlockingCall scoped_blocking_call(
184 FROM_HERE, base::BlockingType::MAY_BLOCK);
185 task_runner_->PostTask(
186 FROM_HERE, base::Bind(&HidServiceFreeBSD::RemoveDevice, service_,
187 device_id));
188 }
189
190 private:
191
CheckPendingPermissionChange()192 void CheckPendingPermissionChange() {
193 base::internal::AssertBlockingAllowed();
194 std::map<std::string, int>::iterator it;
195 for (it = permissions_checks_attempts_.begin(); it != permissions_checks_attempts_.end();) {
196 std::string device_name = it->first;
197 bool keep = true;
198 if (HaveReadWritePermissions(device_name)) {
199 OnDeviceAdded(device_name);
200 keep = false;
201 }
202 else if (it->second-- <= 0) {
203 HID_LOG(ERROR) << "Still don't have write permissions to '" << device_name
204 << "' after " << kMaxPermissionChecks << " attempts";
205 keep = false;
206 }
207
208 if (keep)
209 ++it;
210 else
211 permissions_checks_attempts_.erase(it++);
212 }
213
214 if (permissions_checks_attempts_.empty())
215 timer_->Stop();
216 }
217
SetupDevdMonitor()218 void SetupDevdMonitor() {
219 base::internal::AssertBlockingAllowed();
220
221 int devd_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
222 if (devd_fd < 0)
223 return;
224
225 struct sockaddr_un sa;
226
227 sa.sun_family = AF_UNIX;
228 strlcpy(sa.sun_path, "/var/run/devd.seqpacket.pipe", sizeof(sa.sun_path));
229 if (connect(devd_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
230 close(devd_fd);
231 return;
232 }
233
234 devd_fd_.reset(devd_fd);
235 file_watcher_ = base::FileDescriptorWatcher::WatchReadable(
236 devd_fd_.get(), base::Bind(&BlockingTaskRunnerHelper::OnDevdMessageCanBeRead,
237 base::Unretained(this)));
238 }
239
OnDevdMessageCanBeRead()240 void OnDevdMessageCanBeRead() {
241 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
242 ssize_t bytes_read = HANDLE_EINTR(recv(devd_fd_.get(), devd_buffer_->data(),
243 devd_buffer_->size() - 1, MSG_WAITALL));
244 if (bytes_read < 0) {
245 if (errno != EAGAIN) {
246 HID_LOG(ERROR) << "Read failed";
247 file_watcher_.reset();
248 }
249 return;
250 }
251
252 devd_buffer_->data()[bytes_read] = 0;
253 char *data = devd_buffer_->data();
254 // It may take some time for devd to change permissions
255 // on /dev/uhidX node. So do not fail immediately if
256 // open fail. Retry each second for kMaxPermissionChecks
257 // times before giving up entirely
258 if (base::StartsWith(data, "+uhid", base::CompareCase::SENSITIVE)) {
259 std::vector<std::string> parts = base::SplitString(
260 data, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
261 if (!parts.empty()) {
262 std::string device_name = parts[0].substr(1); // skip '+'
263 if (HaveReadWritePermissions(device_name))
264 OnDeviceAdded(parts[0].substr(1));
265 else {
266 // Do not re-add to checks
267 if (permissions_checks_attempts_.find(device_name) == permissions_checks_attempts_.end()) {
268 permissions_checks_attempts_.insert(std::pair<std::string, int>(device_name, kMaxPermissionChecks));
269 timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(1),
270 this, &BlockingTaskRunnerHelper::CheckPendingPermissionChange);
271 }
272 }
273 }
274 }
275
276 if (base::StartsWith(data, "-uhid", base::CompareCase::SENSITIVE)) {
277 std::vector<std::string> parts = base::SplitString(
278 data, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
279 if (!parts.empty()) {
280 std::string device_name = parts[0].substr(1); // skip '-'
281 auto it = permissions_checks_attempts_.find(device_name);
282 if (it != permissions_checks_attempts_.end()) {
283 permissions_checks_attempts_.erase(it);
284 if (permissions_checks_attempts_.empty())
285 timer_->Stop();
286 }
287 OnDeviceRemoved(parts[0].substr(1));
288 }
289 }
290 }
291
292 SEQUENCE_CHECKER(sequence_checker_);
293
294 // This weak pointer is only valid when checked on this task runner.
295 base::WeakPtr<HidServiceFreeBSD> service_;
296 scoped_refptr<base::SequencedTaskRunner> task_runner_;
297 std::unique_ptr<base::FileDescriptorWatcher::Controller> file_watcher_;
298 std::unique_ptr<base::RepeatingTimer> timer_;
299 base::ScopedFD devd_fd_;
300 scoped_refptr<net::IOBufferWithSize> devd_buffer_;
301 std::map<std::string, int> permissions_checks_attempts_;
302
303 DISALLOW_COPY_AND_ASSIGN(BlockingTaskRunnerHelper);
304 };
305
HidServiceFreeBSD()306 HidServiceFreeBSD::HidServiceFreeBSD()
307 : blocking_task_runner_(
308 base::ThreadPool::CreateSequencedTaskRunner(kBlockingTaskTraits)),
309 helper_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner_)) {
310 helper_.reset(new BlockingTaskRunnerHelper(weak_factory_.GetWeakPtr()));
311 blocking_task_runner_->PostTask(
312 FROM_HERE,
313 base::BindOnce(&BlockingTaskRunnerHelper::Start, base::Unretained(helper_.get())));
314 }
315
~HidServiceFreeBSD()316 HidServiceFreeBSD::~HidServiceFreeBSD() {
317 blocking_task_runner_->DeleteSoon(FROM_HERE, helper_.release());
318 }
319
GetWeakPtr()320 base::WeakPtr<HidService> HidServiceFreeBSD::GetWeakPtr() {
321 return weak_factory_.GetWeakPtr();
322 }
323
324 // static
OpenOnBlockingThread(std::unique_ptr<ConnectParams> params)325 void HidServiceFreeBSD::OpenOnBlockingThread(
326 std::unique_ptr<ConnectParams> params) {
327 base::ScopedBlockingCall scoped_blocking_call(
328 FROM_HERE, base::BlockingType::MAY_BLOCK);
329 scoped_refptr<base::SequencedTaskRunner> task_runner = params->task_runner;
330
331 base::FilePath device_path(params->device_info->device_node());
332 base::File device_file;
333 int flags =
334 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE;
335 device_file.Initialize(device_path, flags);
336 if (!device_file.IsValid()) {
337 HID_LOG(EVENT) << "Failed to open '" << params->device_info->device_node()
338 << "': "
339 << base::File::ErrorToString(device_file.error_details());
340 task_runner->PostTask(FROM_HERE,
341 base::BindOnce(std::move(params->callback), nullptr));
342 return;
343 }
344 params->fd.reset(device_file.TakePlatformFile());
345 task_runner->PostTask(FROM_HERE, base::BindOnce(&HidServiceFreeBSD::FinishOpen,
346 std::move(params)));
347 }
348
Connect(const std::string & device_guid,ConnectCallback callback)349 void HidServiceFreeBSD::Connect(const std::string& device_guid,
350 ConnectCallback callback) {
351 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
352
353 const auto& map_entry = devices().find(device_guid);
354 if (map_entry == devices().end()) {
355 base::ThreadTaskRunnerHandle::Get()->PostTask(
356 FROM_HERE, base::BindOnce(std::move(callback), nullptr));
357 return;
358 }
359
360 scoped_refptr<HidDeviceInfo> device_info = map_entry->second;
361
362 auto params = std::make_unique<ConnectParams>(device_info, std::move(callback));
363 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner =
364 params->blocking_task_runner;
365
366 blocking_task_runner->PostTask(
367 FROM_HERE, base::BindOnce(&HidServiceFreeBSD::OpenOnBlockingThread,
368 std::move(params)));
369 }
370
371 // static
FinishOpen(std::unique_ptr<ConnectParams> params)372 void HidServiceFreeBSD::FinishOpen(std::unique_ptr<ConnectParams> params) {
373 DCHECK(params->fd.is_valid());
374
375 if (!base::SetNonBlocking(params->fd.get())) {
376 HID_PLOG(ERROR) << "Failed to set the non-blocking flag on the device fd";
377 std::move(params->callback).Run(nullptr);
378 }
379
380 std::move(params->callback).Run(base::MakeRefCounted<HidConnectionFreeBSD>(
381 std::move(params->device_info),
382 std::move(params->fd),
383 std::move(params->blocking_task_runner)
384 ));
385 }
386
387 } // namespace device
388