一、什么是uevent机制(纯概念)
uevent是Linux内核中用于通知用户空间程序的一种机制,它属于Kobject的一部分。
uevent的主要功能是在设备或Kobject状态发生变化时,例如设备的添加、移除或属性变化等,向用户空间程序发送通知。这些通知可以被用来动态创建或删除设备节点,或者触发其他用户空间的响应动作。
在Linux设备驱动开发中,uevent机制是非常重要的一部分。它允许内核和用户空间之间进行有效的通信,使得用户空间能够及时了解设备状态的变化并做出相应的处理。这比早期需要手动使用mknod命令来创建设备节点的方式要高效和自动化得多。
总的来说,uevent是Linux内核与用户空间交互的一个重要机制,它通过Kobject子系统实现,为设备管理提供了动态、自动化的解决方案。
二、监听uevent
当然我们不是要重新造轮子去监听uevent事件,而是利用现有的机制udev,它是内核中的设备管理守护进程,通过监听uevent事件来动态管理/dev目录下的设备文件。从这句话我们应该可以感觉到,诶这东西肯定可以监听到外接设备的热插拔事件,因为它会确保/dev目录下只会存在目前真正有的设备节点,超级方便!!(虽然现在在任何高版本设备上都是基操了)。
三、实现
既然是监听uevent事件,我们就要找到uevent事件相关的文件,在内核核心文件中找寻,system/core/下放了Android核心功能的实现,我们也在这找到了ueventd.cpp文件,目录在
system/core/init/ueventd.cpp
void ColdBoot::Run() {
android::base::Timer cold_boot_timer;
RegenerateUevents();
ForkSubProcesses();
DoRestoreCon();
WaitForSubProcesses();
close(open(COLDBOOT_DONE, O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
LOG(INFO) << "Coldboot took " << cold_boot_timer.duration().count() / 1000.0f << " seconds";
}
我们可以在上面这段代码加入检测uevent启动时候判断,防止开机时外接设备直接连接上我们的Android机器,例如我现在在Run函数中使用一个新的函数DelayCheckUsbDeviceList(),具体实现如下
void ColdBoot::DelayCheckUsbDeviceList(){
auto pid = fork();
if (pid < 0) {
PLOG(FATAL) << "DelayCheckUsbDeviceList fork() failed!\n";
return;
}
if (pid == 0) {
device_handler_.systemReady_ = 0;
PLOG(INFO) << "DelayCheckUsbDeviceList fork() success! device_handler_.usb_device_list_ready_ = " << device_handler_.systemReady_ << " \n";
std::string listpath = "/vender/oem/usb_device_white_list";
while(1){
if(!android::base::GetBoolProperty("ro.system.ready", false) || access(listpath.c_str(), F_OK) != 0){
PLOG(INFO) << "DelayCheckUsbDeviceList system partition not ready, continue waiting\n";
sleep(2);
continue;
}
PLOG(INFO) << "DelayCheckUsbDeviceList system ready!\n";
device_handler_.systemReady_ = 1;
RegenerateUevents();
ForkSubProcesses();
DoRestoreCon();
WaitForSubProcesses();
//for (unsigned int i = 0; i < device_handler_.usb_uevent_queue_.size(); i++) {
// auto& uevent = device_handler_.usb_uevent_queue_[i];
// device_handler_.HandleDeviceEvent(uevent);
//}
break;
}
PLOG(FATAL) << "DelayCheckUsbDeviceList fork() exit!\n";
_exit(EXIT_SUCCESS);
}
}
在这里检测判断后,我们系统在准备好之前是不会生成相应的外设节点的,只有在准备好后才会通过已经设置好的白名单一一对应生成节点通过设备的使用。在ueventd_main函数中,会通过device_handler.HandleDeviceEvent检测每一个连入的设备,那么我们在HandleDeviceEvent函数中也加个判断,这个函数在system/core/init/device.cpp文件中,在同一路径下的device.h文件中,这个函数已经被注册在了DeviceHandler域名下,所以可以直接调用到。在HandleDeviceEvent中,我们加入下面这段代码
if((strstr(uevent.path.c_str(),"usb") != NULL) && (uevent.action == "add")){
if(android::base::GetBoolProperty("persist.system.device.control",true)){
if(!isUsbDeviceWhiteList(uevent)){
return;
}
}
}
在这里我们有几重判断
- 是否是usb设备并且是否是插入行为
- 是否在Android属性中有打开控制开关(定制化行为)
- 是否在白名单内
根据以上判断,我们就可以准备的避开不需要的设备连接,避免高危行为。所以最重要的就是这里面的isUsbDeviceWhiteList函数,这里面有什么内容呢?
static int str_to_digit(const char *string, enum value_type type)
{
int value = 0;
char buf[100];
strncpy(buf, string, sizeof(buf));
if (type == ATTR_VAL_HEX) {
while(*string) {
if(!isxdigit(*string++))
return -1;
}
if (sscanf(buf, "%x", &value) < 1)
return -1;
} else {
while(*string) {
if(!isdigit(*string++))
return -1;
}
value = atoi(buf);
}
return value;
}
static char* usb_device_get_attr_str(const char *devpath, const char *attr)
{
char buf[100];
char node_name[200];
FILE *fp;
char str_len;
snprintf(node_name, sizeof(node_name), "%s/%s", devpath, attr);
fp = fopen(node_name, "r");
if (fp == NULL) {
PLOG(ERROR) << "Failed to open " << node_name;
return NULL;
}
if (!fgets(buf, sizeof(buf), fp)) {
PLOG(ERROR) << "Failed to get " << node_name;
fclose(fp);
return NULL;
}
str_len = strlen(buf);
if (buf[str_len-1] == '\n')
buf[str_len-1] = '\0';
fclose(fp);
return strdup(buf);
}
static int usb_device_get_attr_val(const char *devpath, const char *attr,
enum value_type type)
{
char *attr_str;
int value = 0;
attr_str = usb_device_get_attr_str(devpath, attr);
if (!attr_str)
return -1;
value = str_to_digit(attr_str, type);
if (value < 0)
PLOG(ERROR) << attr_str << " is illegal string!";
free(attr_str);
return value;
}
static char* usb_device_get_attr_str_from_name(int bus,int devnum,const char *attr)
{
char portname[5];
char subportname[5];
char portdir[50];
DIR *devdir;
struct dirent *de;
snprintf(subportname, sizeof(subportname), "%d-1", bus);
snprintf(portname, sizeof(portname), "usb%d", bus);
devdir = opendir(USB_DEV_DIR);
if(devdir == 0) {
PLOG(ERROR) << USB_DEV_DIR << " does not exist!";
return NULL;
}
while ((de = readdir(devdir))) {
if ((!strncmp(de->d_name, subportname, 3) &&
!strchr(de->d_name, ':')) ||
!strncmp(de->d_name, portname, 4)) {
snprintf(portdir, sizeof(portdir), USB_DEV_DIR "/%s", de->d_name);
if (devnum == usb_device_get_attr_val(portdir, USB_DEV_NUM,
ATTR_VAL_DEC)) {
closedir(devdir);
return usb_device_get_attr_str(portdir, attr);
}
}
}
closedir(devdir);
return NULL;
}
static int usb_device_get_attr_val_from_name(int bus,int devnum,const char *attr,
enum value_type type)
{
char *attr_str;
int value = 0;
attr_str = usb_device_get_attr_str_from_name(bus,devnum, attr);
if (!attr_str)
return -1;
value = str_to_digit(attr_str, type);
if (value < 0)
PLOG(ERROR) << attr_str << " is illegal string!";
free(attr_str);
return value;
}
static int findStrTotal(const std::string& msg,const char* subStr){
std::size_t pos = -1;
int totalNum = 0;
do{
pos = msg.find(subStr,pos + 1);
if(pos == std::string::npos){
return totalNum;
}
totalNum++;
}while(1);
}
static int findStrPos(const std::string& msg,const char* subStr,int num){
std::size_t pos = -1;
for (int i = 0; i < num; ++i)
{
pos = msg.find(subStr,pos + 1);
if(pos == std::string::npos){
return -1;
}
}
return pos;
}
bool DeviceHandler::isUsbDeivceWhiteList(const Uevent& uevent){
char vidpid[20] = {0};
// int bus = uevent.minor / 128 + 1;
// int devnum = uevent.minor % 128 + 1;
const char* path = uevent.path.c_str();
int idProduct = -1;
int idVendor = -1;
std::string fullPath = "/sys"+uevent.path+"/";
int totalSeparator = findStrTotal(fullPath,"/");
for (int i = totalSeparator - 1; i >= 0; i--)
{
int tempPos = findStrPos(fullPath,"/",i + 1);
if(tempPos >= 0){
fullPath = fullPath.substr(0,tempPos);
if(fullPath.find(":",0) != std::string::npos){
continue;
}
idProduct = usb_device_get_attr_val(fullPath.c_str(),USB_DEV_PID,ATTR_VAL_HEX);
idVendor = usb_device_get_attr_val(fullPath.c_str(), USB_DEV_VID, ATTR_VAL_HEX);
if(idProduct > 0 && idVendor > 0){
break;
}
}
}
if(idProduct <= 0 || idVendor <= 0){
return true;
}
sprintf(vidpid,"%04x:%04x",idVendor,idProduct);
if(!android::base::GetBoolProperty("ro.system.ready", false)){
}
auto file_contents = std::string();
std::string listpath = USB_DEV_WHITE_LIST_PATH;
if (access(listpath.c_str(), F_OK) != 0 || !ReadFileToString(USB_DEV_WHITE_LIST_PATH, &file_contents)) {
//auto file_contents = std::string();
//if (!ReadFileToString(USB_DEV_WHITE_LIST_PATH, &file_contents)) {
return false;
}
if (file_contents.empty()) {
return false;
}
if(strstr(file_contents.c_str(),vidpid) == NULL){
char len[1024];
return false;
}
return true;
}
有兴趣的读者可以慢慢分析以上代码,至此我们就完成了动态的usb设备连接管控