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"
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)
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";
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
),
53 task_runner(task_runner
),
54 file_task_runner(file_task_runner
) {}
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
{
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(
90 base::Bind(&HidServiceLinux::FirstEnumerationComplete
, self
->service_
));
92 // |self| is now owned by the current message loop.
93 ignore_result(self
.release());
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
);
104 HidDeviceId device_id
= device_path
;
106 const char* subsystem
= udev_device_get_subsystem(device
);
107 if (!subsystem
|| strcmp(subsystem
, kHidrawSubsystem
) != 0) {
111 const char* str_property
= udev_device_get_devnode(device
);
115 std::string device_node
= str_property
;
117 udev_device
* parent
= udev_device_get_parent(device
);
122 const char* hid_id
= udev_device_get_property_value(parent
, kHIDID
);
127 std::vector
<std::string
> parts
= base::SplitString(
128 hid_id
, ":", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
129 if (parts
.size() != 3) {
133 uint32_t int_property
= 0;
134 if (!HexStringToUInt(base::StringPiece(parts
[1]), &int_property
) ||
135 int_property
> std::numeric_limits
<uint16_t>::max()) {
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()) {
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
) {
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
)) {
170 scoped_refptr
<HidDeviceInfo
> device_info(new HidDeviceInfoLinux(
171 device_id
, device_node
, vendor_id
, product_id
, product_name
,
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
);
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());
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));
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.";
242 device_info
->device_node(),
243 base::Bind(&HidServiceLinux::OnPathOpened
, base::Passed(¶ms
)));
245 file_task_runner_
->PostTask(FROM_HERE
,
246 base::Bind(&HidServiceLinux::OpenOnBlockingThread
,
247 base::Passed(¶ms
)));
248 #endif // defined(OS_CHROMEOS)
251 #if defined(OS_CHROMEOS)
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(¶ms
), base::Passed(&fd
)));
264 void HidServiceLinux::ValidateFdOnBlockingThread(
265 scoped_ptr
<ConnectParams
> params
,
266 dbus::FileDescriptor fd
) {
267 base::ThreadRestrictions::AssertIOAllowed();
271 params
->device_file
= base::File(fd
.TakeValue());
272 FinishOpen(params
.Pass());
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));
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
;
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
) {
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()
306 << base::File::ErrorToString(device_file
.error_details());
307 task_runner
->PostTask(FROM_HERE
, base::Bind(params
->callback
, nullptr));
311 FinishOpen(params
.Pass());
314 #endif // defined(OS_CHROMEOS)
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());
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));
328 task_runner
->PostTask(
330 base::Bind(&HidServiceLinux::CreateConnection
, base::Passed(¶ms
)));
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