blob: 50b9feaf6ac51b9dde28666a20eea1beed196127 [file] [log] [blame]
// Copyright (c) 2013 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 "power_manager/powerd/system/udev.h"
#include <libudev.h>
#include <utility>
#include <base/logging.h>
#include <base/memory/free_deleter.h>
#include "power_manager/common/power_constants.h"
#include "power_manager/powerd/system/tagged_device.h"
#include "power_manager/powerd/system/udev_subsystem_observer.h"
#include "power_manager/powerd/system/udev_tagged_device_observer.h"
#include "power_manager/powerd/system/udev_util.h"
namespace power_manager {
namespace system {
namespace {
static const char kPowerdUdevTag[] = "powerd";
static const char kPowerdTagsVar[] = "POWERD_TAGS";
UdevEvent::Action StrToAction(const char* action_str) {
if (!action_str)
return UdevEvent::Action::UNKNOWN;
else if (strcmp(action_str, "add") == 0)
return UdevEvent::Action::ADD;
else if (strcmp(action_str, "remove") == 0)
return UdevEvent::Action::REMOVE;
else if (strcmp(action_str, "change") == 0)
return UdevEvent::Action::CHANGE;
else if (strcmp(action_str, "online") == 0)
return UdevEvent::Action::ONLINE;
else if (strcmp(action_str, "offline") == 0)
return UdevEvent::Action::OFFLINE;
else
return UdevEvent::Action::UNKNOWN;
}
bool GetDeviceInfo(struct udev_device* dev, UdevDeviceInfo* device_info_out) {
DCHECK(device_info_out);
const char* subsystem = udev_device_get_subsystem(dev);
if (!subsystem)
return false;
device_info_out->subsystem = subsystem;
const char* devtype = udev_device_get_devtype(dev);
if (devtype)
device_info_out->devtype = devtype;
const char* sysname = udev_device_get_sysname(dev);
if (sysname)
device_info_out->sysname = sysname;
const char* syspath = udev_device_get_syspath(dev);
if (syspath)
device_info_out->syspath = syspath;
return true;
}
struct UdevDeviceDeleter {
void operator()(udev_device* dev) {
if (dev)
udev_device_unref(dev);
}
};
}; // namespace
Udev::Udev() : udev_(NULL), udev_monitor_(NULL) {}
Udev::~Udev() {
if (udev_monitor_)
udev_monitor_unref(udev_monitor_);
if (udev_)
udev_unref(udev_);
}
bool Udev::Init() {
udev_ = udev_new();
if (!udev_) {
PLOG(ERROR) << "udev_new() failed";
return false;
}
udev_monitor_ = udev_monitor_new_from_netlink(udev_, "udev");
if (!udev_monitor_) {
PLOG(ERROR) << "udev_monitor_new_from_netlink() failed";
return false;
}
if (udev_monitor_filter_add_match_tag(udev_monitor_, kPowerdUdevTag))
LOG(ERROR) << "udev_monitor_filter_add_match_tag failed";
if (udev_monitor_filter_update(udev_monitor_))
LOG(ERROR) << "udev_monitor_filter_update failed";
udev_monitor_enable_receiving(udev_monitor_);
int fd = udev_monitor_get_fd(udev_monitor_);
if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
fd, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this)) {
LOG(ERROR) << "Unable to watch FD " << fd;
return false;
}
LOG(INFO) << "Watching FD " << fd << " for udev events";
EnumerateTaggedDevices();
return true;
}
void Udev::AddSubsystemObserver(const std::string& subsystem,
UdevSubsystemObserver* observer) {
DCHECK(udev_) << "Object uninitialized";
DCHECK(observer);
auto it = subsystem_observers_.find(subsystem);
if (it == subsystem_observers_.end()) {
it = subsystem_observers_
.emplace(
subsystem,
std::make_unique<base::ObserverList<UdevSubsystemObserver>>())
.first;
}
it->second->AddObserver(observer);
}
void Udev::RemoveSubsystemObserver(const std::string& subsystem,
UdevSubsystemObserver* observer) {
DCHECK(observer);
auto it = subsystem_observers_.find(subsystem);
if (it != subsystem_observers_.end())
it->second->RemoveObserver(observer);
}
void Udev::AddTaggedDeviceObserver(UdevTaggedDeviceObserver* observer) {
tagged_device_observers_.AddObserver(observer);
}
void Udev::RemoveTaggedDeviceObserver(UdevTaggedDeviceObserver* observer) {
tagged_device_observers_.RemoveObserver(observer);
}
std::vector<TaggedDevice> Udev::GetTaggedDevices() {
std::vector<TaggedDevice> devices;
devices.reserve(tagged_devices_.size());
for (const std::pair<std::string, TaggedDevice>& pair : tagged_devices_)
devices.push_back(pair.second);
return devices;
}
bool Udev::GetSubsystemDevices(const std::string& subsystem,
std::vector<UdevDeviceInfo>* devices_out) {
DCHECK(udev_);
DCHECK(devices_out);
struct udev_enumerate* enumerate = udev_enumerate_new(udev_);
if (!enumerate) {
LOG(ERROR) << "udev_enumerate_new failed";
return false;
}
int ret = udev_enumerate_add_match_subsystem(enumerate, subsystem.c_str());
if (ret != 0) {
LOG(ERROR) << "udev_enumerate_add_match_subsystem failed. Error: "
<< strerror(-ret);
udev_enumerate_unref(enumerate);
return false;
}
ret = udev_enumerate_scan_devices(enumerate);
if (ret != 0) {
LOG(ERROR) << "udev_enumerate_scan_devices failed. Error: "
<< strerror(-ret);
udev_enumerate_unref(enumerate);
return false;
}
devices_out->clear();
for (struct udev_list_entry* list_entry =
udev_enumerate_get_list_entry(enumerate);
list_entry != NULL; list_entry = udev_list_entry_get_next(list_entry)) {
const char* syspath = udev_list_entry_get_name(list_entry);
struct udev_device* device = udev_device_new_from_syspath(udev_, syspath);
if (!device) {
LOG(ERROR) << "Enumeration of device with syspath " << syspath
<< " failed";
continue;
}
UdevDeviceInfo device_info;
if (GetDeviceInfo(device, &device_info)) {
devices_out->push_back(std::move(device_info));
} else {
LOG(ERROR) << "Could not retrieve Udev info for the device with syspath "
<< syspath;
}
udev_device_unref(device);
}
udev_enumerate_unref(enumerate);
return true;
}
bool Udev::GetSysattr(const std::string& syspath,
const std::string& sysattr,
std::string* value) {
DCHECK(udev_);
DCHECK(value);
value->clear();
struct udev_device* device =
udev_device_new_from_syspath(udev_, syspath.c_str());
if (!device) {
LOG(WARNING) << "Failed to open udev device: " << syspath;
return false;
}
const char* value_cstr =
udev_device_get_sysattr_value(device, sysattr.c_str());
if (value_cstr)
*value = value_cstr;
udev_device_unref(device);
return value_cstr != NULL;
}
bool Udev::SetSysattr(const std::string& syspath,
const std::string& sysattr,
const std::string& value) {
DCHECK(udev_);
struct udev_device* device =
udev_device_new_from_syspath(udev_, syspath.c_str());
if (!device) {
LOG(WARNING) << "Failed to open udev device: " << syspath;
return false;
}
// udev can modify this value, hence we copy it first.
std::unique_ptr<char, base::FreeDeleter> value_mutable(strdup(value.c_str()));
int rv = udev_device_set_sysattr_value(device, sysattr.c_str(),
value_mutable.get());
udev_device_unref(device);
if (rv != 0) {
LOG(WARNING) << "Failed to set sysattr '" << sysattr << "' on device "
<< syspath << ": " << strerror(-rv);
return false;
}
return true;
}
bool Udev::FindParentWithSysattr(const std::string& syspath,
const std::string& sysattr,
const std::string& stop_at_devtype,
std::string* parent_syspath) {
DCHECK(udev_);
struct udev_device* device =
udev_device_new_from_syspath(udev_, syspath.c_str());
if (!device) {
LOG(WARNING) << "Failed to open udev device: " << syspath;
return false;
}
struct udev_device* parent = device;
while (parent) {
const char* value = udev_device_get_sysattr_value(parent, sysattr.c_str());
const char* devtype = udev_device_get_devtype(parent);
if (value)
break;
// Go up one level unless the devtype matches stop_at_devtype.
if (devtype && strcmp(stop_at_devtype.c_str(), devtype) == 0)
parent = nullptr;
else
parent = udev_device_get_parent(parent);
}
if (parent)
*parent_syspath = udev_device_get_syspath(parent);
udev_device_unref(device);
return parent != NULL;
}
bool Udev::GetDevlinks(const std::string& syspath,
std::vector<std::string>* out) {
DCHECK(udev_);
DCHECK(out);
std::unique_ptr<udev_device, UdevDeviceDeleter> device(
udev_device_new_from_syspath(udev_, syspath.c_str()));
if (!device) {
PLOG(WARNING) << "Failed to open udev device: " << syspath;
return false;
}
out->clear();
// TODO(egranata): maybe write a wrapper around udev_list to support
// for(entry : list) {...}
struct udev_list_entry* devlink =
udev_device_get_devlinks_list_entry(device.get());
while (devlink) {
const char* name = udev_list_entry_get_name(devlink);
if (name)
out->push_back(name);
devlink = udev_list_entry_get_next(devlink);
}
return true;
}
void Udev::OnFileCanReadWithoutBlocking(int fd) {
struct udev_device* dev = udev_monitor_receive_device(udev_monitor_);
if (!dev)
return;
const char* subsystem = udev_device_get_subsystem(dev);
const char* sysname = udev_device_get_sysname(dev);
const char* action_str = udev_device_get_action(dev);
UdevEvent::Action action = StrToAction(action_str);
VLOG(1) << "Received event: subsystem=" << subsystem << " sysname=" << sysname
<< " action=" << action_str;
HandleSubsystemEvent(action, dev);
HandleTaggedDevice(action, dev);
udev_device_unref(dev);
}
void Udev::OnFileCanWriteWithoutBlocking(int fd) {
NOTREACHED() << "Unexpected non-blocking write notification for FD " << fd;
}
void Udev::HandleSubsystemEvent(UdevEvent::Action action,
struct udev_device* dev) {
UdevEvent event;
if (!GetDeviceInfo(dev, &(event.device_info)))
return;
event.action = action;
auto it = subsystem_observers_.find(event.device_info.subsystem);
if (it != subsystem_observers_.end()) {
for (UdevSubsystemObserver& observer : *it->second)
observer.OnUdevEvent(event);
}
}
void Udev::HandleTaggedDevice(UdevEvent::Action action,
struct udev_device* dev) {
if (!udev_device_has_tag(dev, kPowerdUdevTag))
return;
const char* syspath = udev_device_get_syspath(dev);
const char* tags = udev_device_get_property_value(dev, kPowerdTagsVar);
switch (action) {
case UdevEvent::Action::ADD:
case UdevEvent::Action::CHANGE:
TaggedDeviceChanged(syspath, tags ? tags : "");
break;
case UdevEvent::Action::REMOVE:
TaggedDeviceRemoved(syspath);
break;
default:
break;
}
}
void Udev::TaggedDeviceChanged(const std::string& syspath,
const std::string& tags) {
if (!tags.empty()) {
LOG(INFO) << (tagged_devices_.count(syspath) ? "Updating" : "Adding")
<< " device " << syspath << " with tags " << tags;
}
// Replace existing device with same syspath.
tagged_devices_[syspath] = TaggedDevice(syspath, tags);
const TaggedDevice& device = tagged_devices_[syspath];
for (UdevTaggedDeviceObserver& observer : tagged_device_observers_)
observer.OnTaggedDeviceChanged(device);
}
void Udev::TaggedDeviceRemoved(const std::string& syspath) {
TaggedDevice device = tagged_devices_[syspath];
if (!device.tags().empty())
LOG(INFO) << "Removing device " << syspath;
tagged_devices_.erase(syspath);
for (UdevTaggedDeviceObserver& observer : tagged_device_observers_)
observer.OnTaggedDeviceRemoved(device);
}
bool Udev::EnumerateTaggedDevices() {
DCHECK(udev_);
struct udev_enumerate* enumerate = udev_enumerate_new(udev_);
if (!enumerate) {
LOG(ERROR) << "udev_enumerate_new failed";
return false;
}
if (udev_enumerate_add_match_tag(enumerate, kPowerdUdevTag) != 0) {
LOG(ERROR) << "udev_enumerate_add_match_tag failed";
udev_enumerate_unref(enumerate);
return false;
}
if (udev_enumerate_scan_devices(enumerate) != 0) {
LOG(ERROR) << "udev_enumerate_scan_devices failed";
udev_enumerate_unref(enumerate);
return false;
}
tagged_devices_.clear();
struct udev_list_entry* entry = NULL;
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) {
const char* syspath = udev_list_entry_get_name(entry);
struct udev_device* device = udev_device_new_from_syspath(udev_, syspath);
if (!device) {
LOG(ERROR) << "Enumerated device does not exist: " << syspath;
continue;
}
const char* tags_cstr =
udev_device_get_property_value(device, kPowerdTagsVar);
const std::string tags = tags_cstr ? tags_cstr : "";
if (!tags.empty())
LOG(INFO) << "Adding device " << syspath << " with tags " << tags;
tagged_devices_[syspath] = TaggedDevice(syspath, tags);
udev_device_unref(device);
}
udev_enumerate_unref(enumerate);
return true;
}
} // namespace system
} // namespace power_manager