Commit a3cc23d0 authored by jan.koester's avatar jan.koester
Browse files

test

parent d5f705b9
Loading
Loading
Loading
Loading
+41 −3
Changes for commands/modutils/depmod.cpp: 41 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -62,6 +62,12 @@ namespace fresh { namespace cmd {
    // module built as part of one coherent kernel tree, which is the only
    // case this system's own kernel build produces) -- not a general
    // replacement for depmod's symbol-resolution fallback.
    //
    // Also writes modules.alias ("alias <pattern> <module>", each .ko's
    // "alias=" fields), which modprobe and syslite resolve modaliases and
    // request_module() names with. Paths in modules.dep are relative to the
    // module directory, like real depmod's: with -b (modules_install into
    // a buildroot) absolute ones would point into the buildroot.
    class DepmodCommand : public ShellCommand {
    public:
        DepmodCommand():ShellCommand("depmod"){}
@@ -133,6 +139,22 @@ namespace fresh { namespace cmd {
            return "";
        }

        // "alias=" may occur many times, modinfoField() returns the first
        static std::vector<std::string> modinfoFields(const std::vector<char> &data,const std::string &field){
            std::vector<std::string> out;
            size_t i=0;
            while(i<data.size()){
                size_t start=i;
                while(i<data.size() && data[i]!='\0') ++i;
                std::string entry(data.data()+start,i-start);
                ++i;
                size_t eq=entry.find('=');
                if(eq!=std::string::npos && entry.compare(0,eq,field)==0 && eq==field.size())
                    out.push_back(entry.substr(eq+1));
            }
            return out;
        }

        static std::vector<std::string> splitComma(const std::string &s){
            std::vector<std::string> out;
            size_t start=0;
@@ -219,9 +241,14 @@ namespace fresh { namespace cmd {
            std::vector<std::string> koFiles;
            findKoFiles(dir,koFiles);

            // "kernel/fs/x.ko": relative to dir
            auto relPath=[&](const std::string &path){
                return path.compare(0,dir.size()+1,dir+"/")==0 ? path.substr(dir.size()+1) : path;
            };

            std::map<std::string,std::string> nameToPath;
            for(const auto &path: koFiles)
                nameToPath[moduleName(path)]=path;
                nameToPath[moduleName(path)]=relPath(path);

            std::string depPath=dir+"/modules.dep";
            std::ofstream out(depPath);
@@ -229,14 +256,25 @@ namespace fresh { namespace cmd {
                std::cerr << "depmod: " << depPath << ": " << strerror(errno) << std::endl;
                return 1;
            }
            std::string aliasPath=dir+"/modules.alias";
            std::ofstream aliases(aliasPath);
            if(!aliases.is_open()){
                std::cerr << "depmod: " << aliasPath << ": " << strerror(errno) << std::endl;
                return 1;
            }
            aliases << "# Aliases extracted from modules themselves.\n";

            for(const auto &path: koFiles){
                std::vector<char> info;
                std::string depends;
                if(readModinfoSectionFromFile(path,info))
                std::string name=moduleName(path);
                if(readModinfoSectionFromFile(path,info)){
                    depends=modinfoField(info,"depends");
                    for(const auto &a: modinfoFields(info,"alias"))
                        aliases << "alias " << a << " " << name << "\n";
                }

                out << path << ":";
                out << relPath(path) << ":";
                for(const auto &depName: splitComma(depends)){
                    auto it=nameToPath.find(depName);
                    if(it!=nameToPath.end())
+278 −42
Changes for commands/modutils/modprobe.cpp: 278 added lines, 42 removed lines.
Original line number Diff line number Diff line
@@ -39,6 +39,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <unistd.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <dirent.h>
#include <sys/syscall.h>
#include <sys/utsname.h>

@@ -55,12 +57,39 @@ namespace fresh { namespace cmd {
            return s.size()>kXzSuffixLen &&
                   s.compare(s.size()-kXzSuffixLen,kXzSuffixLen,kXzSuffix)==0;
        }

        // kmod's alias_normalize(): '-' and '_' are the same in module names
        // and aliases, except inside a [...] class of a pattern
        std::string normalize(const std::string &s){
            std::string out=s;
            bool inClass=false;
            for(auto &c: out){
                if(c=='[') inClass=true;
                else if(c==']') inClass=false;
                else if(c=='-' && !inClass) c='_';
            }
            return out;
        }

        struct Alias {
            std::string pattern; // normalized
            std::string module;
        };
    }

    // Loads MODULE (by name, not path) plus its dependencies, resolved from
    // modules.dep (see depmod). Idempotent: a module already loaded (EEXIST
    // from finit_module/init_module) counts as success, matching real
    // modprobe.
    // Loads modules by name or alias, their dependencies first, from the
    // index depmod writes (modules.dep, modules.alias) plus the kernel's own
    // modules.builtin and modules.builtin.modinfo: a module or alias that
    // is built in counts as loaded. Also the kernel's request_module()
    // helper (CONFIG_MODPROBE_PATH), which runs "modprobe -q -- <alias>"
    // for fs-nfsd, rtnl-link-bridge, net-pf-10, md-raid1, ...
    //
    // modprobe.d (/etc, /run, /usr/lib, /lib): "options <module> <params>",
    // "alias <pattern> <module>", "blacklist <module>" (the module's aliases
    // are ignored; with -b also its name); "install", "remove" and
    // "softdep" are ignored. Kernel command line: modprobe.blacklist=a,b and
    // <module>.<param>=<value>. An already loaded module (EEXIST) counts as
    // success.
    class ModprobeCommand : public ShellCommand {
    public:
        ModprobeCommand():ShellCommand("modprobe"){}
@@ -104,7 +133,7 @@ namespace fresh { namespace cmd {
                        errno==EEXIST;
                return ok;
            }
            int fd=open(koPath.c_str(),O_RDONLY);
            int fd=open(koPath.c_str(),O_RDONLY|O_CLOEXEC);
            if(fd<0) return false;
            bool ok=(syscall(SYS_finit_module,fd,params.c_str(),0)==0) || errno==EEXIST;
            close(fd);
@@ -112,75 +141,282 @@ namespace fresh { namespace cmd {
        }

        int runCommand(int argc,const char **argv){
            if(argc<2){
                std::cerr << "modprobe: usage: modprobe MODULE [param=value ...]" << std::endl;
            bool quiet=false,verbose=false,dryRun=false,all=false,remove=false,useBlacklist=false;
            std::vector<std::string> args;
            int i=1;
            for(;i<argc;++i){
                std::string a=argv[i];
                if(a=="--"){ ++i; break; }
                if(a.size()<2 || a[0]!='-') break;
                if(a=="--quiet") quiet=true;
                else if(a=="--verbose") verbose=true;
                else if(a=="--dry-run" || a=="--show") dryRun=true;
                else if(a=="--all") all=true;
                else if(a=="--remove") remove=true;
                else if(a=="--use-blacklist") useBlacklist=true;
                else if(a[1]=='-'){
                    std::cerr << "modprobe: unknown option " << a << std::endl;
                    return 1;
                }else{
                    for(size_t j=1;j<a.size();++j){
                        switch(a[j]){
                        case 'q': quiet=true; break;
                        case 'v': verbose=true; break;
                        case 'n': dryRun=true; break;
                        case 'a': all=true; break;
                        case 'r': remove=true; break;
                        case 'b': useBlacklist=true; break;
                        default:
                            std::cerr << "modprobe: unknown option -" << a[j] << std::endl;
                            return 1;
                        }
                    }
                }
            }
            for(;i<argc;++i) args.push_back(argv[i]);
            if(args.empty()){
                std::cerr << "modprobe: usage: modprobe [-qvnb] MODULE [param=value ...]" << std::endl
                          << "       modprobe -a [-qvnb] MODULE..." << std::endl
                          << "       modprobe -r [-qvn] MODULE..." << std::endl;
                return 1;
            }
            std::string wanted=argv[1];
            for(auto &c: wanted) if(c=='-') c='_';

            // MODULE [params] -- with -a or -r every argument is a module
            std::vector<std::string> wanted;
            std::string params;
            for(int i=2;i<argc;++i){
            if(all || remove){
                wanted=args;
            }else{
                wanted.push_back(args[0]);
                for(size_t k=1;k<args.size();++k){
                    if(!params.empty()) params+=" ";
                params+=argv[i];
                    params+=args[k];
                }
            }

            if(remove){
                int rc=0;
                for(const auto &w: wanted){
                    std::string name=normalize(w);
                    if(verbose) std::cout << "rmmod " << name << std::endl;
                    if(dryRun) continue;
                    if(syscall(SYS_delete_module,name.c_str(),O_NONBLOCK)!=0 && errno!=ENOENT){
                        if(!quiet) std::cerr << "modprobe: " << name << ": " << strerror(errno) << std::endl;
                        rc=1;
                    }
                }
                return rc;
            }

            struct utsname uts;
            uname(&uts);
            std::string depPath=std::string("/lib/modules/")+uts.release+"/modules.dep";
            _dir=std::string("/lib/modules/")+uts.release;
            if(!readIndex(quiet)) return 1;
            readConfig();

            std::ifstream in(depPath);
            if(!in.is_open()){
                std::cerr << "modprobe: " << depPath << ": " << strerror(errno) << std::endl;
                return 1;
            int rc=0;
            for(const auto &w: wanted)
                if(!probe(w,params,quiet,verbose,dryRun,useBlacklist)) rc=1;
            return rc;
        }

            std::map<std::string,std::string> nameToPath;
            std::map<std::string,std::vector<std::string> > deps;
    private:
        std::string _dir;
        std::map<std::string,std::string> _nameToPath;
        std::map<std::string,std::vector<std::string> > _deps;
        std::vector<Alias> _aliases;        // modules.alias
        std::vector<Alias> _confAliases;    // modprobe.d
        std::set<std::string> _builtin;
        std::vector<std::string> _builtinAliases; // normalized patterns
        std::set<std::string> _blacklist;
        std::map<std::string,std::string> _options;

        std::string absPath(const std::string &p) const {
            return (!p.empty() && p[0]=='/') ? p : _dir+"/"+p;
        }

        bool readIndex(bool quiet){
            std::string depPath=_dir+"/modules.dep";
            std::ifstream in(depPath);
            if(!in.is_open()){
                if(!quiet) std::cerr << "modprobe: " << depPath << ": " << strerror(errno) << std::endl;
                return false;
            }
            std::string line;
            while(std::getline(in,line)){
                size_t colon=line.find(':');
                if(colon==std::string::npos) continue;
                std::string path=line.substr(0,colon);
                std::string name=moduleName(path);
                nameToPath[name]=path;

                _nameToPath[name]=absPath(path);
                std::istringstream rest(line.substr(colon+1));
                std::string depPathTok;
                while(rest >> depPathTok)
                    deps[name].push_back(moduleName(depPathTok));
                    _deps[name].push_back(moduleName(depPathTok));
            }

            auto it=nameToPath.find(wanted);
            if(it==nameToPath.end()){
                std::cerr << "modprobe: " << wanted << ": module not found" << std::endl;
                return 1;
            std::ifstream alias(_dir+"/modules.alias");
            while(std::getline(alias,line)){
                std::istringstream iss(line);
                std::string kw,pattern,module;
                if(!(iss >> kw >> pattern >> module) || kw!="alias") continue;
                _aliases.push_back({normalize(pattern),normalize(module)});
            }

            std::set<std::string> loaded;
            std::vector<std::string> order; // dependencies first, target last
            std::ifstream builtin(_dir+"/modules.builtin");
            while(std::getline(builtin,line))
                if(!line.empty()) _builtin.insert(moduleName(line));

            // "<module>.alias=<pattern>" NUL-separated, from the kernel build
            std::ifstream binfo(_dir+"/modules.builtin.modinfo",std::ios::binary);
            std::string entry;
            while(std::getline(binfo,entry,'\0')){
                size_t dot=entry.find('.');
                if(dot==std::string::npos || entry.compare(dot,7,".alias=")!=0) continue;
                _builtinAliases.push_back(normalize(entry.substr(dot+7)));
            }
            return true;
        }

            std::function<void(const std::string &)> visit=[&](const std::string &name){
                if(loaded.count(name)) return;
                loaded.insert(name);
                auto dit=deps.find(name);
                if(dit!=deps.end())
        static std::vector<std::string> confFiles(){
            std::map<std::string,std::string> files; // an earlier dir hides the same name
            for(const char *d: {"/etc/modprobe.d","/run/modprobe.d","/usr/lib/modprobe.d","/lib/modprobe.d"}){
                DIR *dir=opendir(d);
                if(!dir) continue;
                struct dirent *ent;
                while((ent=readdir(dir))!=nullptr){
                    std::string n=ent->d_name;
                    if(n.size()>5 && n.compare(n.size()-5,5,".conf")==0)
                        files.emplace(n,std::string(d)+"/"+n);
                }
                closedir(dir);
            }
            std::vector<std::string> out;
            for(auto &f: files) out.push_back(f.second);
            return out;
        }

        void addOptions(const std::string &module,const std::string &opts){
            if(opts.empty()) return;
            std::string &o=_options[normalize(module)];
            if(!o.empty()) o+=" ";
            o+=opts;
        }

        void readConfig(){
            for(const auto &file: confFiles()){
                std::ifstream in(file);
                std::string line;
                while(std::getline(in,line)){
                    std::istringstream iss(line);
                    std::string kw,a;
                    if(!(iss >> kw) || kw[0]=='#') continue;
                    if(!(iss >> a)) continue;
                    if(kw=="blacklist"){
                        _blacklist.insert(normalize(a));
                    }else if(kw=="alias"){
                        std::string module;
                        if(iss >> module) _confAliases.push_back({normalize(a),normalize(module)});
                    }else if(kw=="options"){
                        std::string rest;
                        std::getline(iss,rest);
                        size_t b=rest.find_first_not_of(" \t");
                        addOptions(a,b==std::string::npos ? "" : rest.substr(b));
                    }
                }
            }
            std::ifstream cmdline("/proc/cmdline");
            std::string tok;
            while(cmdline >> tok){
                size_t eq=tok.find('=');
                if(eq==std::string::npos) continue;
                std::string key=tok.substr(0,eq),val=tok.substr(eq+1);
                if(key=="modprobe.blacklist"){
                    std::istringstream ms(val);
                    std::string m;
                    while(std::getline(ms,m,','))
                        if(!m.empty()) _blacklist.insert(normalize(m));
                    continue;
                }
                size_t dot=key.find('.');
                if(dot==std::string::npos || dot==0) continue;
                addOptions(key.substr(0,dot),tok.substr(dot+1));
            }
        }

        static bool matches(const std::string &pattern,const std::string &name){
            return fnmatch(pattern.c_str(),name.c_str(),0)==0;
        }

        // the modules NAME stands for; builtin: NAME is (an alias of) a
        // built-in module
        std::vector<std::string> resolve(const std::string &name,bool useBlacklist,bool &builtin) const {
            std::string n=normalize(name);
            std::vector<std::string> out;
            builtin=false;
            if(_nameToPath.count(n)){
                if(!(useBlacklist && _blacklist.count(n))) out.push_back(n);
                return out;
            }
            if(_builtin.count(n)){
                builtin=true;
                return out;
            }
            std::set<std::string> seen;
            for(const auto *list: {&_confAliases,&_aliases}){
                for(const auto &a: *list){
                    if(!matches(a.pattern,n) || _blacklist.count(a.module)) continue;
                    if(_builtin.count(a.module)){ builtin=true; continue; }
                    if(seen.insert(a.module).second) out.push_back(a.module);
                }
                if(!out.empty()) return out; // modprobe.d's aliases win
            }
            if(out.empty())
                for(const auto &p: _builtinAliases)
                    if(matches(p,n)){ builtin=true; break; }
            return out;
        }

        bool probe(const std::string &name,const std::string &params,
                   bool quiet,bool verbose,bool dryRun,bool useBlacklist){
            bool builtin=false;
            std::vector<std::string> modules=resolve(name,useBlacklist,builtin);
            if(modules.empty()){
                if(builtin) return true;
                if(!quiet) std::cerr << "modprobe: " << name << ": module not found" << std::endl;
                return false;
            }

            bool ok=true;
            for(const auto &target: modules){
                std::set<std::string> visited;
                std::vector<std::string> order; // dependencies first, target last
                std::function<void(const std::string &)> visit=[&](const std::string &m){
                    if(!visited.insert(m).second) return;
                    auto dit=_deps.find(m);
                    if(dit!=_deps.end())
                        for(const auto &dep: dit->second) visit(dep);
                order.push_back(name);
                    order.push_back(m);
                };
            visit(wanted);
                visit(target);

            for(const auto &name: order){
                auto pit=nameToPath.find(name);
                if(pit==nameToPath.end()) continue; // unresolved dep name -- best-effort, matches real modprobe's tolerance for soft deps
                if(!loadOne(pit->second,name==wanted ? params : "")){
                    std::cerr << "modprobe: " << name << ": " << strerror(errno) << std::endl;
                    return 1;
                for(const auto &m: order){
                    auto pit=_nameToPath.find(m);
                    if(pit==_nameToPath.end()) continue; // a dependency that is built in
                    std::string p;
                    auto oit=_options.find(m);
                    if(oit!=_options.end()) p=oit->second;
                    if(m==target && !params.empty()) p+=(p.empty() ? "" : " ")+params;
                    if(verbose) std::cout << "insmod " << pit->second << (p.empty() ? "" : " ") << p << std::endl;
                    if(dryRun) continue;
                    if(!loadOne(pit->second,p)){
                        if(!quiet) std::cerr << "modprobe: " << m << ": " << strerror(errno) << std::endl;
                        ok=false;
                        break;
                    }
                }

            return 0;
            }
            return ok;
        }
    };