blob: d9bc27a6a0128988f0996c10127624368b860f09 [file] [log] [blame]
/*
* 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() {
if (!thread_.IsRunning()) {
// No-op destructor if the watcher thread is not running. It could happen
// if Start() failed or is not getting called at all.
return;
}
// 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::MessagePumpType::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