Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / device / hid / hid_service_linux.cc
blob36abff28a42d7dea0e9213a8b158948bcb7fb8d3
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.
5 #include "device/hid/hid_service_linux.h"
7 #include <fcntl.h>
8 #include <limits>
9 #include <string>
11 #include "base/bind.h"
12 #include "base/files/file.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/location.h"
16 #include "base/scoped_observer.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "components/device_event_log/device_event_log.h"
22 #include "device/hid/device_monitor_linux.h"
23 #include "device/hid/hid_connection_linux.h"
24 #include "device/hid/hid_device_info_linux.h"
25 #include "device/udev_linux/scoped_udev.h"
26 #include "net/base/net_util.h"
28 #if defined(OS_CHROMEOS)
29 #include "base/sys_info.h"
30 #include "chromeos/dbus/dbus_thread_manager.h"
31 #include "chromeos/dbus/permission_broker_client.h"
32 #endif // defined(OS_CHROMEOS)
34 namespace device {
36 namespace {
38 const char kHidrawSubsystem[] = "hidraw";
39 const char kHIDID[] = "HID_ID";
40 const char kHIDName[] = "HID_NAME";
41 const char kHIDUnique[] = "HID_UNIQ";
42 const char kSysfsReportDescriptorKey[] = "report_descriptor";
44 } // namespace
46 struct HidServiceLinux::ConnectParams {
47 ConnectParams(scoped_refptr<HidDeviceInfoLinux> device_info,
48 const ConnectCallback& callback,
49 scoped_refptr<base::SingleThreadTaskRunner> task_runner,
50 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
51 : device_info(device_info),
52 callback(callback),
53 task_runner(task_runner),
54 file_task_runner(file_task_runner) {}
55 ~ConnectParams() {}
57 scoped_refptr<HidDeviceInfoLinux> device_info;
58 ConnectCallback callback;
59 scoped_refptr<base::SingleThreadTaskRunner> task_runner;
60 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner;
61 base::File device_file;
64 class HidServiceLinux::FileThreadHelper
65 : public DeviceMonitorLinux::Observer,
66 public base::MessageLoop::DestructionObserver {
67 public:
68 FileThreadHelper(base::WeakPtr<HidServiceLinux> service,
69 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
70 : observer_(this), service_(service), task_runner_(task_runner) {}
72 ~FileThreadHelper() override {
73 DCHECK(thread_checker_.CalledOnValidThread());
74 base::MessageLoop::current()->RemoveDestructionObserver(this);
77 static void Start(scoped_ptr<FileThreadHelper> self) {
78 base::ThreadRestrictions::AssertIOAllowed();
79 self->thread_checker_.DetachFromThread();
80 // |self| must be added as a destruction observer first so that it will be
81 // notified before DeviceMonitorLinux.
82 base::MessageLoop::current()->AddDestructionObserver(self.get());
84 DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance();
85 self->observer_.Add(monitor);
86 monitor->Enumerate(base::Bind(&FileThreadHelper::OnDeviceAdded,
87 base::Unretained(self.get())));
88 self->task_runner_->PostTask(
89 FROM_HERE,
90 base::Bind(&HidServiceLinux::FirstEnumerationComplete, self->service_));
92 // |self| is now owned by the current message loop.
93 ignore_result(self.release());
96 private:
97 // DeviceMonitorLinux::Observer:
98 void OnDeviceAdded(udev_device* device) override {
99 DCHECK(thread_checker_.CalledOnValidThread());
100 const char* device_path = udev_device_get_syspath(device);
101 if (!device_path) {
102 return;
104 HidDeviceId device_id = device_path;
106 const char* subsystem = udev_device_get_subsystem(device);
107 if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0) {
108 return;
111 const char* str_property = udev_device_get_devnode(device);
112 if (!str_property) {
113 return;
115 std::string device_node = str_property;
117 udev_device* parent = udev_device_get_parent(device);
118 if (!parent) {
119 return;
122 const char* hid_id = udev_device_get_property_value(parent, kHIDID);
123 if (!hid_id) {
124 return;
127 std::vector<std::string> parts = base::SplitString(
128 hid_id, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
129 if (parts.size() != 3) {
130 return;
133 uint32_t int_property = 0;
134 if (!HexStringToUInt(base::StringPiece(parts[1]), &int_property) ||
135 int_property > std::numeric_limits<uint16_t>::max()) {
136 return;
138 uint16_t vendor_id = int_property;
140 if (!HexStringToUInt(base::StringPiece(parts[2]), &int_property) ||
141 int_property > std::numeric_limits<uint16_t>::max()) {
142 return;
144 uint16_t product_id = int_property;
146 std::string serial_number;
147 str_property = udev_device_get_property_value(parent, kHIDUnique);
148 if (str_property != NULL) {
149 serial_number = str_property;
152 std::string product_name;
153 str_property = udev_device_get_property_value(parent, kHIDName);
154 if (str_property != NULL) {
155 product_name = str_property;
158 const char* parent_sysfs_path = udev_device_get_syspath(parent);
159 if (!parent_sysfs_path) {
160 return;
162 base::FilePath report_descriptor_path =
163 base::FilePath(parent_sysfs_path).Append(kSysfsReportDescriptorKey);
164 std::string report_descriptor_str;
165 if (!base::ReadFileToString(report_descriptor_path,
166 &report_descriptor_str)) {
167 return;
170 scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfoLinux(
171 device_id, device_node, vendor_id, product_id, product_name,
172 serial_number,
173 kHIDBusTypeUSB, // TODO(reillyg): Detect Bluetooth. crbug.com/443335
174 std::vector<uint8>(report_descriptor_str.begin(),
175 report_descriptor_str.end())));
177 task_runner_->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::AddDevice,
178 service_, device_info));
181 void OnDeviceRemoved(udev_device* device) override {
182 DCHECK(thread_checker_.CalledOnValidThread());
183 const char* device_path = udev_device_get_syspath(device);
184 if (device_path) {
185 task_runner_->PostTask(
186 FROM_HERE, base::Bind(&HidServiceLinux::RemoveDevice, service_,
187 std::string(device_path)));
191 // base::MessageLoop::DestructionObserver:
192 void WillDestroyCurrentMessageLoop() override {
193 DCHECK(thread_checker_.CalledOnValidThread());
194 delete this;
197 base::ThreadChecker thread_checker_;
198 ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_;
200 // This weak pointer is only valid when checked on this task runner.
201 base::WeakPtr<HidServiceLinux> service_;
202 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
204 DISALLOW_COPY_AND_ASSIGN(FileThreadHelper);
207 HidServiceLinux::HidServiceLinux(
208 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
209 : file_task_runner_(file_task_runner), weak_factory_(this) {
210 task_runner_ = base::ThreadTaskRunnerHandle::Get();
211 scoped_ptr<FileThreadHelper> helper(
212 new FileThreadHelper(weak_factory_.GetWeakPtr(), task_runner_));
213 helper_ = helper.get();
214 file_task_runner_->PostTask(
215 FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Passed(&helper)));
218 HidServiceLinux::~HidServiceLinux() {
219 file_task_runner_->DeleteSoon(FROM_HERE, helper_);
222 void HidServiceLinux::Connect(const HidDeviceId& device_id,
223 const ConnectCallback& callback) {
224 DCHECK(thread_checker_.CalledOnValidThread());
226 const auto& map_entry = devices().find(device_id);
227 if (map_entry == devices().end()) {
228 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
229 return;
231 scoped_refptr<HidDeviceInfoLinux> device_info =
232 static_cast<HidDeviceInfoLinux*>(map_entry->second.get());
234 scoped_ptr<ConnectParams> params(new ConnectParams(
235 device_info, callback, task_runner_, file_task_runner_));
237 #if defined(OS_CHROMEOS)
238 chromeos::PermissionBrokerClient* client =
239 chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
240 DCHECK(client) << "Could not get permission broker client.";
241 client->OpenPath(
242 device_info->device_node(),
243 base::Bind(&HidServiceLinux::OnPathOpened, base::Passed(&params)));
244 #else
245 file_task_runner_->PostTask(FROM_HERE,
246 base::Bind(&HidServiceLinux::OpenOnBlockingThread,
247 base::Passed(&params)));
248 #endif // defined(OS_CHROMEOS)
251 #if defined(OS_CHROMEOS)
253 // static
254 void HidServiceLinux::OnPathOpened(scoped_ptr<ConnectParams> params,
255 dbus::FileDescriptor fd) {
256 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner =
257 params->file_task_runner;
258 file_task_runner->PostTask(
259 FROM_HERE, base::Bind(&HidServiceLinux::ValidateFdOnBlockingThread,
260 base::Passed(&params), base::Passed(&fd)));
263 // static
264 void HidServiceLinux::ValidateFdOnBlockingThread(
265 scoped_ptr<ConnectParams> params,
266 dbus::FileDescriptor fd) {
267 base::ThreadRestrictions::AssertIOAllowed();
269 fd.CheckValidity();
270 if (fd.is_valid()) {
271 params->device_file = base::File(fd.TakeValue());
272 FinishOpen(params.Pass());
273 } else {
274 HID_LOG(EVENT) << "Permission broker denied access to '"
275 << params->device_info->device_node() << "'.";
276 params->task_runner->PostTask(FROM_HERE,
277 base::Bind(params->callback, nullptr));
281 #else
283 // static
284 void HidServiceLinux::OpenOnBlockingThread(scoped_ptr<ConnectParams> params) {
285 base::ThreadRestrictions::AssertIOAllowed();
286 scoped_refptr<base::SingleThreadTaskRunner> task_runner = params->task_runner;
288 base::FilePath device_path(params->device_info->device_node());
289 base::File& device_file = params->device_file;
290 int flags =
291 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE;
292 device_file.Initialize(device_path, flags);
293 if (!device_file.IsValid()) {
294 base::File::Error file_error = device_file.error_details();
296 if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) {
297 HID_LOG(EVENT)
298 << "Access denied opening device read-write, trying read-only.";
299 flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
300 device_file.Initialize(device_path, flags);
303 if (!device_file.IsValid()) {
304 HID_LOG(EVENT) << "Failed to open '" << params->device_info->device_node()
305 << "': "
306 << base::File::ErrorToString(device_file.error_details());
307 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr));
308 return;
311 FinishOpen(params.Pass());
314 #endif // defined(OS_CHROMEOS)
316 // static
317 void HidServiceLinux::FinishOpen(scoped_ptr<ConnectParams> params) {
318 base::ThreadRestrictions::AssertIOAllowed();
319 scoped_refptr<base::SingleThreadTaskRunner> task_runner = params->task_runner;
321 int result = net::SetNonBlocking(params->device_file.GetPlatformFile());
322 if (result == -1) {
323 HID_PLOG(ERROR) << "Failed to set the non-blocking flag on the device fd";
324 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr));
325 return;
328 task_runner->PostTask(
329 FROM_HERE,
330 base::Bind(&HidServiceLinux::CreateConnection, base::Passed(&params)));
333 // static
334 void HidServiceLinux::CreateConnection(scoped_ptr<ConnectParams> params) {
335 DCHECK(params->device_file.IsValid());
336 params->callback.Run(make_scoped_refptr(
337 new HidConnectionLinux(params->device_info, params->device_file.Pass(),
338 params->file_task_runner)));
341 } // namespace device