// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "device/usb/usb_service_linux.h" #include #include #include "base/bind.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" #include "device/udev_linux/udev_watcher.h" #include "device/usb/usb_device_handle.h" #include "device/usb/usb_device_linux.h" #include "device/usb/webusb_descriptors.h" namespace device { namespace { // Standard USB requests and descriptor types: const uint16_t kUsbVersion2_1 = 0x0210; const uint8_t kDeviceClassHub = 0x09; void OnReadDescriptors(const base::Callback& callback, scoped_refptr device_handle, const GURL& landing_page) { UsbDeviceLinux* device = static_cast(device_handle->GetDevice().get()); if (landing_page.is_valid()) device->set_webusb_landing_page(landing_page); device_handle->Close(); callback.Run(true /* success */); } void OnDeviceOpenedToReadDescriptors( const base::Callback& callback, scoped_refptr device_handle) { if (device_handle) { ReadWebUsbDescriptors( device_handle, base::Bind(&OnReadDescriptors, callback, device_handle)); } else { callback.Run(false /* failure */); } } } // namespace class UsbServiceLinux::FileThreadHelper : public UdevWatcher::Observer { public: FileThreadHelper(base::WeakPtr service); ~FileThreadHelper() override; void Start(); private: // UdevWatcher::Observer void OnDeviceAdded(ScopedUdevDevicePtr device) override; void OnDeviceRemoved(ScopedUdevDevicePtr device) override; std::unique_ptr watcher_; // |service_| can only be checked for validity on |task_runner_|'s sequence. base::WeakPtr service_; scoped_refptr task_runner_; base::SequenceChecker sequence_checker_; DISALLOW_COPY_AND_ASSIGN(FileThreadHelper); }; UsbServiceLinux::FileThreadHelper::FileThreadHelper( base::WeakPtr service) : service_(service), task_runner_(base::SequencedTaskRunnerHandle::Get()) { // Detaches from the sequence on which this object was created. It will be // bound to its owning sequence when Start() is called. sequence_checker_.DetachFromSequence(); } UsbServiceLinux::FileThreadHelper::~FileThreadHelper() { DCHECK(sequence_checker_.CalledOnValidSequence()); } // static void UsbServiceLinux::FileThreadHelper::Start() { DCHECK(sequence_checker_.CalledOnValidSequence()); base::ThreadRestrictions::AssertIOAllowed(); watcher_ = UdevWatcher::StartWatching(this); watcher_->EnumerateExistingDevices(); task_runner_->PostTask(FROM_HERE, base::Bind(&UsbServiceLinux::HelperStarted, service_)); } void UsbServiceLinux::FileThreadHelper::OnDeviceAdded( ScopedUdevDevicePtr device) { DCHECK(sequence_checker_.CalledOnValidSequence()); const char* subsystem = udev_device_get_subsystem(device.get()); if (!subsystem || strcmp(subsystem, "usb") != 0) return; const char* value = udev_device_get_devnode(device.get()); if (!value) return; std::string device_path = value; const char* sysfs_path = udev_device_get_syspath(device.get()); if (!sysfs_path) return; base::FilePath descriptors_path = base::FilePath(sysfs_path).Append("descriptors"); std::string descriptors_str; if (!base::ReadFileToString(descriptors_path, &descriptors_str)) return; UsbDeviceDescriptor descriptor; if (!descriptor.Parse(std::vector(descriptors_str.begin(), descriptors_str.end()))) { return; } if (descriptor.device_class == kDeviceClassHub) { // Don't try to enumerate hubs. We never want to connect to a hub. return; } std::string manufacturer; value = udev_device_get_sysattr_value(device.get(), "manufacturer"); if (value) manufacturer = value; std::string product; value = udev_device_get_sysattr_value(device.get(), "product"); if (value) product = value; std::string serial_number; value = udev_device_get_sysattr_value(device.get(), "serial"); if (value) serial_number = value; unsigned active_configuration = 0; value = udev_device_get_sysattr_value(device.get(), "bConfigurationValue"); if (value) base::StringToUint(value, &active_configuration); task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceLinux::OnDeviceAdded, service_, device_path, descriptor, manufacturer, product, serial_number, active_configuration)); } void UsbServiceLinux::FileThreadHelper::OnDeviceRemoved( ScopedUdevDevicePtr device) { DCHECK(sequence_checker_.CalledOnValidSequence()); const char* device_path = udev_device_get_devnode(device.get()); if (device_path) { task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceLinux::OnDeviceRemoved, service_, std::string(device_path))); } } UsbServiceLinux::UsbServiceLinux() : UsbService(CreateBlockingTaskRunner()), weak_factory_(this) { helper_ = base::MakeUnique(weak_factory_.GetWeakPtr()); blocking_task_runner()->PostTask( FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Unretained(helper_.get()))); } UsbServiceLinux::~UsbServiceLinux() { blocking_task_runner()->DeleteSoon(FROM_HERE, helper_.release()); } void UsbServiceLinux::GetDevices(const GetDevicesCallback& callback) { DCHECK(CalledOnValidThread()); if (enumeration_ready()) UsbService::GetDevices(callback); else enumeration_callbacks_.push_back(callback); } void UsbServiceLinux::OnDeviceAdded(const std::string& device_path, const UsbDeviceDescriptor& descriptor, const std::string& manufacturer, const std::string& product, const std::string& serial_number, uint8_t active_configuration) { DCHECK(CalledOnValidThread()); if (ContainsKey(devices_by_path_, device_path)) { USB_LOG(ERROR) << "Got duplicate add event for path: " << device_path; return; } // Devices that appear during initial enumeration are gathered into the first // result returned by GetDevices() and prevent device add/remove notifications // from being sent. if (!enumeration_ready()) ++first_enumeration_countdown_; scoped_refptr device( new UsbDeviceLinux(device_path, descriptor, manufacturer, product, serial_number, active_configuration)); devices_by_path_[device->device_path()] = device; if (device->usb_version() >= kUsbVersion2_1) { device->Open(base::Bind(&OnDeviceOpenedToReadDescriptors, base::Bind(&UsbServiceLinux::DeviceReady, weak_factory_.GetWeakPtr(), device))); } else { DeviceReady(device, true /* success */); } } void UsbServiceLinux::DeviceReady(scoped_refptr device, bool success) { DCHECK(CalledOnValidThread()); bool enumeration_became_ready = false; if (!enumeration_ready()) { DCHECK_GT(first_enumeration_countdown_, 0u); first_enumeration_countdown_--; if (enumeration_ready()) enumeration_became_ready = true; } // If |device| was disconnected while descriptors were being read then it // will have been removed from |devices_by_path_|. auto it = devices_by_path_.find(device->device_path()); if (it == devices_by_path_.end()) { success = false; } else if (success) { DCHECK(!base::ContainsKey(devices(), device->guid())); devices()[device->guid()] = device; USB_LOG(USER) << "USB device added: path=" << device->device_path() << " vendor=" << device->vendor_id() << " \"" << device->manufacturer_string() << "\", product=" << device->product_id() << " \"" << device->product_string() << "\", serial=\"" << device->serial_number() << "\", guid=" << device->guid(); } else { devices_by_path_.erase(it); } if (enumeration_became_ready) { std::vector> result; result.reserve(devices().size()); for (const auto& map_entry : devices()) result.push_back(map_entry.second); for (const auto& callback : enumeration_callbacks_) callback.Run(result); enumeration_callbacks_.clear(); } else if (success && enumeration_ready()) { NotifyDeviceAdded(device); } } void UsbServiceLinux::OnDeviceRemoved(const std::string& path) { DCHECK(CalledOnValidThread()); auto by_path_it = devices_by_path_.find(path); if (by_path_it == devices_by_path_.end()) return; scoped_refptr device = by_path_it->second; devices_by_path_.erase(by_path_it); device->OnDisconnect(); auto by_guid_it = devices().find(device->guid()); if (by_guid_it != devices().end() && enumeration_ready()) { USB_LOG(USER) << "USB device removed: path=" << device->device_path() << " guid=" << device->guid(); devices().erase(by_guid_it); NotifyDeviceRemoved(device); } } void UsbServiceLinux::HelperStarted() { DCHECK(CalledOnValidThread()); helper_started_ = true; if (enumeration_ready()) { std::vector> result; result.reserve(devices().size()); for (const auto& map_entry : devices()) result.push_back(map_entry.second); for (const auto& callback : enumeration_callbacks_) callback.Run(result); enumeration_callbacks_.clear(); } } } // namespace device