|
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 |