/*******************************************************************************
this code is protected by the GNU affero GPLv3
author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
*******************************************************************************/
#include <libkmod.h>
#include <ulinux/compiler_types.h>
#include <ulinux/types.h>
#include <ulinux/sysc.h>
#include <ulinux/mmap.h>
#include <ulinux/error.h>
#include <ulinux/file.h>
#include <ulinux/fs.h>
#include <ulinux/dirent.h>
#include <ulinux/utils/mem.h>
#include <ulinux/utils/ascii/string/string.h>
#include "out.h"
#include "ulinux_namespace.h"
#include "static_modules.h"
static struct kmod_ctx *kmod_ctx;
static u8 is_current(u8 *n)
{
if(n[0]=='.'&&n[1]==0) return 1;
return 0;
}
static u8 is_parent(u8 *n)
{
if(n[0]=='.'&&n[1]=='.'&&n[2]==0) return 1;
return 0;
}
/*that for some kmod stuff*/
#define NULL 0
static void driver_modules_probe(u8 *modalias)
{
i r;
i log_prio;
struct kmod_list *list;
struct kmod_list *list_entry;
list=0;
/*shutdown libkmod output since we expect a lot of error/warning messages*/
log_prio=kmod_get_log_priority(kmod_ctx);
kmod_set_log_priority(kmod_ctx,0);
r=kmod_module_new_from_lookup(kmod_ctx,(const char*)modalias,&list);
kmod_set_log_priority(kmod_ctx,log_prio);
if(r<0) return;/*we may not have any alias for this hardware, skipping*/
log_prio=kmod_get_log_priority(kmod_ctx);
/*same than above*/
kmod_set_log_priority(kmod_ctx,0);
kmod_list_foreach(list_entry,list){
struct kmod_module *m;
m=kmod_module_get_module(list_entry);
/*try to probe the driver module, may not be there, keep going in any case*/
kmod_module_probe_insert_module(m,KMOD_PROBE_IGNORE_COMMAND,0,0,0,0);
kmod_module_unref(m);
}
kmod_set_log_priority(kmod_ctx,log_prio);
kmod_module_unref_list(list);
}
#define MODALIAS_SZ_MAX PAGE_SZ/*sysfs attributes are of page sz*/
static void modalias_process(i parent_fd)
{
i fd;
l r;
u8 modalias[MODALIAS_SZ_MAX];
loop{
fd=(i)ul_openat(parent_fd,"modalias",RDONLY|NONBLOCK);
if(fd!=-EINTR) break;
}
if(ISERR(fd)){
OUT("WARNING(%d):unable to open modalias, skipping\n",fd);
return;
}
/*the size of a page size reported by the sysfs filesystem is not revelant
of its content size*/
loop{
r=read(fd,modalias,MODALIAS_SZ_MAX);
if(r!=-EINTR&&r!=-EAGAIN) break;
}
if(ISERR(r)){
OUT("ERROR(%ld):unable to read modalias file\n",r);
goto close_fd;
}
modalias[r-1]=0;/*replace the modalias \n terminating char with 0*/
driver_modules_probe(modalias);
close_fd:
loop{
r=close(fd);
if(r!=-EINTR) break;
}
}
#define MODALIAS_FOUND 1
#define MODALIAS_MISSING 0
static u8 modalias_search(u8 *dirents,l ds_sz)
{
l d_u8_idx=0;
u8 r=MODALIAS_MISSING;
loop{
struct dirent64 *d;
s8 cmp;
if(d_u8_idx>=ds_sz) break;
d=(struct dirent64*)(dirents+d_u8_idx);
cmp=strcmp("modalias",d->name);
if(cmp==0){
r=MODALIAS_FOUND;
break;
}
d_u8_idx+=d->rec_len;
}
return r;
}
/*forward declaration*/
static void sys_devices_parse(i parent_fd);
static void real_subdirs_recurse(i parent_fd,u8 *dirents,l ds_sz)
{
l d_u8_idx=0;
loop{
struct dirent64 *d;
if(d_u8_idx>=ds_sz) break;
d=(struct dirent64*)(dirents+d_u8_idx);
if(d->type==DT_DIR&&!is_current(d->name)&&!is_parent(d->name)){
i subdir_fd;
loop{
subdir_fd=(i)ul_openat(parent_fd,d->name,RDONLY|NONBLOCK);
if(subdir_fd!=-EINTR) break;
}
if(ISERR(subdir_fd))
OUT("WARNING(%d):unable to open subdir:%s:skipping\n",subdir_fd,
d->name);
else{
sys_devices_parse(subdir_fd);
loop{
l r;
r=close(subdir_fd);
if(r!=-EINTR) break;
}
}
}
d_u8_idx+=d->rec_len;
}
}
#define DIRENTS_BUF_SZ 8192
/*
The dentry type is supported by sysfs. Top-down parsing, we load the tree-upper
driver modules first.
*/
static void sys_devices_parse(i parent_fd)
{
u8 dirents[DIRENTS_BUF_SZ];
l ds_sz;
u8 have_modalias;
ds_sz=getdents64(parent_fd,dirents,DIRENTS_BUF_SZ);
if(ISERR(ds_sz)){
OUT("ERROR(%ld):getdents error\n",ds_sz);
exit_group(-1);
}
if(!ds_sz) return;/*no dirents*/
have_modalias=modalias_search(dirents,ds_sz);
if(have_modalias==MODALIAS_FOUND) modalias_process(parent_fd);
real_subdirs_recurse(parent_fd,dirents,ds_sz);
}
void modules_probe_name(u8 *name)
{
i r;
struct kmod_module *m;
r=kmod_module_new_from_name(kmod_ctx,(const char*)name,&m);
if(r<0){
OUT("ERROR(%d):unable to create a module description for %s, skipping\n",
name);
return;
}
r=kmod_module_probe_insert_module(m,KMOD_PROBE_IGNORE_COMMAND,0,0,0,0);
if(r!=0){
OUT("ERROR(%d):unable to probe module %s, skipping\n",r,name);
goto unref_module;
}
unref_module:
kmod_module_unref(m);
}
/*probe upper layer disk modules and user extra modules*/
void modules_probe_static(void)
{
u8 **m_name;
m_name=&static_modules[0];
loop{
if(*m_name==0) break;
modules_probe_name(*m_name);
++m_name;
}
}
void modules_setup(void)
{
kmod_ctx=kmod_new(0,0);
if(!kmod_ctx){
OUT("ERROR:unable to init libkmod\n");
exit_group(-1);
}
}
void modules_cleanup(void)
{
kmod_unref(kmod_ctx);
kmod_ctx=0;
}
void modules_probe_drivers(void)
{
i sys_devices_fd;
loop{
sys_devices_fd=(i)open("/sys/devices",RDONLY|NONBLOCK);
if(sys_devices_fd!=-EINTR) break;
}
if(ISERR(sys_devices_fd)){
OUT("ERROR(%d):unable to open /sys/devices dir\n",sys_devices_fd);
exit_group(-1);
}
sys_devices_parse(sys_devices_fd);
close(sys_devices_fd);
}