| /* |
| * Copyright 2018 The Chromium OS 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 "cros-camera/udev_watcher.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include <libudev.h> |
| |
| #include <base/threading/thread.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| |
| #include "cros-camera/common.h" |
| #include "cros-camera/future.h" |
| |
| namespace cros { |
| |
| UdevWatcher::Observer::~Observer() = default; |
| |
| void UdevWatcher::Observer::OnDeviceAdded(ScopedUdevDevicePtr /*device*/) {} |
| |
| void UdevWatcher::Observer::OnDeviceRemoved(ScopedUdevDevicePtr /*device*/) {} |
| |
| UdevWatcher::UdevWatcher(Observer* observer, std::string subsystem) |
| : observer_(observer), |
| subsystem_(std::move(subsystem)), |
| thread_("UdevWatcherThread") {} |
| |
| UdevWatcher::~UdevWatcher() { |
| // Post a task to reset FileDescriptorWatcher and explicitly call |
| // Thread::Stop() to ensure it is completed before destroying fields. |
| thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&UdevWatcher::StopOnThread, base::Unretained(this))); |
| thread_.Stop(); |
| } |
| |
| bool UdevWatcher::Start( |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| udev_.reset(udev_new()); |
| if (!udev_) { |
| LOGF(ERROR) << "udev_new failed"; |
| return false; |
| } |
| |
| mon_.reset(udev_monitor_new_from_netlink(udev_.get(), "udev")); |
| if (!mon_) { |
| LOGF(ERROR) << "udev_monitor_new_from_netlink failed"; |
| return false; |
| } |
| |
| if (udev_monitor_filter_add_match_subsystem_devtype( |
| mon_.get(), subsystem_.c_str(), nullptr) < 0) { |
| LOGF(ERROR) << "udev_monitor_filter_add_match_subsystem_devtype failed"; |
| return false; |
| } |
| |
| if (udev_monitor_enable_receiving(mon_.get()) < 0) { |
| LOGF(ERROR) << "udev_monitor_enable_receiving failed"; |
| return false; |
| } |
| |
| int fd = udev_monitor_get_fd(mon_.get()); |
| if (fd < 0) { |
| LOGF(ERROR) << "udev_monitor_get_fd failed"; |
| return false; |
| } |
| |
| if (!thread_.StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) { |
| LOGF(ERROR) << "thread start failed"; |
| return false; |
| } |
| |
| callback_task_runner_ = task_runner; |
| |
| auto future = cros::Future<bool>::Create(nullptr); |
| thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&UdevWatcher::StartOnThread, base::Unretained(this), |
| fd, cros::GetFutureCallback(future))); |
| return future->Get(); |
| } |
| |
| bool UdevWatcher::EnumerateExistingDevices() { |
| DCHECK(callback_task_runner_) << "Start() shuold be called first"; |
| DCHECK(callback_task_runner_->BelongsToCurrentThread()); |
| |
| ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get())); |
| if (!enumerate) { |
| LOGF(ERROR) << "udev_enumerate_new failed"; |
| return false; |
| } |
| |
| if (udev_enumerate_add_match_subsystem(enumerate.get(), subsystem_.c_str()) < |
| 0) { |
| LOGF(ERROR) << "udev_enumerate_add_match_subsystem failed"; |
| return false; |
| } |
| |
| if (udev_enumerate_scan_devices(enumerate.get()) < 0) { |
| LOGF(ERROR) << "udev_enumerate_scan_devices failed"; |
| return false; |
| } |
| |
| for (udev_list_entry* entry = udev_enumerate_get_list_entry(enumerate.get()); |
| entry != nullptr; entry = udev_list_entry_get_next(entry)) { |
| // We simply ignore errors in some entries here, and keep enumerating. |
| |
| const char* name = udev_list_entry_get_name(entry); |
| if (!name) { |
| LOGF(WARNING) << "udev_list_entry_get_name failed"; |
| continue; |
| } |
| |
| ScopedUdevDevicePtr dev(udev_device_new_from_syspath(udev_.get(), name)); |
| if (!dev) { |
| LOGF(WARNING) << "udev_device_new_from_syspath failed"; |
| continue; |
| } |
| |
| observer_->OnDeviceAdded(std::move(dev)); |
| } |
| |
| return true; |
| } |
| |
| void UdevWatcher::StartOnThread(int fd, base::Callback<void(bool)> callback) { |
| DCHECK(thread_.task_runner()->BelongsToCurrentThread()); |
| |
| watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| fd, |
| base::BindRepeating(&UdevWatcher::OnReadable, base::Unretained(this))); |
| if (!watcher_) { |
| LOGF(ERROR) << "Failed to start watching a file descriptor"; |
| callback.Run(false); |
| return; |
| } |
| |
| callback.Run(true); |
| } |
| |
| void UdevWatcher::StopOnThread() { |
| watcher_ = nullptr; |
| } |
| |
| void UdevWatcher::OnReadable() { |
| DCHECK(thread_.task_runner()->BelongsToCurrentThread()); |
| |
| ScopedUdevDevicePtr dev(udev_monitor_receive_device(mon_.get())); |
| if (!dev) { |
| LOGF(ERROR) << "udev_monitor_receive_device failed"; |
| return; |
| } |
| |
| const char* action = udev_device_get_action(dev.get()); |
| if (!action) { |
| LOGF(ERROR) << "udev_device_get_action failed"; |
| return; |
| } |
| |
| decltype(&Observer::OnDeviceAdded) callback; |
| if (strcmp(action, "add") == 0) { |
| callback = &Observer::OnDeviceAdded; |
| } else if (strcmp(action, "remove") == 0) { |
| callback = &Observer::OnDeviceRemoved; |
| } else { |
| NOTREACHED() << "Unexpected action " << action; |
| return; |
| } |
| |
| callback_task_runner_->PostTask( |
| FROM_HERE, base::Bind(callback, base::Unretained(observer_), |
| base::Passed(std::move(dev)))); |
| } |
| |
| } // namespace cros |