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