#pragma implementation /* #Specification: modules / elf only Module support in linuxconf only work for ELF systems. */ #include #ifndef LINUXCONF_AOUT #include #endif #include #include #include #include #include #include #include #include "misc.h" #include "module.h" #include "misc.m" #include #include #include "../paths.h" #include "libmodules.h" static HELP_FILE help_modules ("misc","modules"); class LINUXCONF_MODULES: public ARRAY{ /*~PROTOBEG~ LINUXCONF_MODULES */ public: LINUXCONF_MODULE *getitem (int no); /*~PROTOEND~ LINUXCONF_MODULES */ }; PUBLIC LINUXCONF_MODULE *LINUXCONF_MODULES::getitem(int no) { return (LINUXCONF_MODULE*)ARRAY::getitem(no); } static LINUXCONF_MODULES modules; PUBLIC LINUXCONF_MODULE::LINUXCONF_MODULE(const char *_name) { context_lock_required(); // Make sure this is available for modules module_api_required(); libmodules_required(); modules.neverdelete(); modules.add (this); name = strdup(_name); } PUBLIC LINUXCONF_MODULE::~LINUXCONF_MODULE() { modules.remove (this); free (name); } PUBLIC const char *LINUXCONF_MODULE::getname() { return name; } PUBLIC void LINUXCONF_MODULE::setname(const char *_name) { free (name); name = strdup(_name); } /* Let the module add its own option to one menu. The "context" let the module identify which dialog it is The module is not forced to add options to the menu. */ PUBLIC VIRTUAL void LINUXCONF_MODULE::setmenu ( DIALOG &dia, MENU_CONTEXT ctx) { M_DIALOG m_dia (&dia); setmenu (m_dia,ctx); } PUBLIC VIRTUAL void LINUXCONF_MODULE::setmenu ( DIALOG &dia, const char *menuid) { M_DIALOG m_dia (&dia); setmenu (m_dia,menuid); } PUBLIC VIRTUAL void LINUXCONF_MODULE::setmenu ( M_DIALOG &, MENU_CONTEXT) { } PUBLIC VIRTUAL void LINUXCONF_MODULE::setmenu ( M_DIALOG &, const char *) { } /* Let the module take control for some html query. */ PUBLIC VIRTUAL int LINUXCONF_MODULE::dohtml (const char *) { return LNCF_NOT_APPLICABLE; } /* Check if the user has selected one menu option related to this module Do nothing most of the time. */ PUBLIC VIRTUAL int LINUXCONF_MODULE::domenu ( MENU_CONTEXT, // context const char *) // key { return 0; } /* Check if the user has selected one menu option related to this module Do nothing most of the time. Variation allowing any menu to be enhance (menus in modules for one) */ PUBLIC VIRTUAL int LINUXCONF_MODULE::domenu ( const char *, // menuid const char *) // key { return 0; } /* Check some file permissions related to the module. Do nothing most of the time. */ PUBLIC VIRTUAL int LINUXCONF_MODULE::fixperm (bool, bool ) { return 0; } /* Give control to the module based on argv[0]. A module foo may setup a symlink to linuxconf like this ln -s /bin/linuxconf /bin/foo and the execution of foo ..., will give control directly to the module. A module not supporting this, or which does not accept argv[0] as an alias to itself will return LNCF_NOT_APPLICABLE. Any other value is the return code and the program (linuxconf) will terminate. */ PUBLIC VIRTUAL int LINUXCONF_MODULE::execmain ( int, // argc char *[], // argv bool) // standalone { return LNCF_NOT_APPLICABLE; } /* A module may participate in a broadcast system where one send a message and modules may react. The amount of interaction is limited though. This can be used to synchronise stuff. */ PUBLIC VIRTUAL int LINUXCONF_MODULE::message ( const char *, // message int, // argc const char *[]) // argv[] { // Do nothing by default return LNCF_NOT_APPLICABLE; } /* This function is called so a module can check is the current operation state of the service it manages matches the configuration state. It must compute the list of commands and actions required to bring the service up to date. Depending on the simul argument, it must do it or simply print it using the net_prtlog function. It returns 0 if all is ok, -1 if any error. */ PUBLIC VIRTUAL int LINUXCONF_MODULE::probe ( int, // Current level being probed // Only service of this level should do something. int, // target network runlevel of the probe // In which runlevel the machine is going to be // after the probe has completed bool) // simulation mode ? { return 0; } /* Add command line usage information to the SSTRING table */ PUBLIC VIRTUAL void LINUXCONF_MODULE::usage(SSTRINGS &) { } /* Check if any module has something to add to this menu (function used by the core) */ void module_setmenu (DIALOG &dia, MENU_CONTEXT context) { int n = modules.getnb(); for (int i=0; isetmenu (dia,context); } /* Check if any module has something to add to this menu (function used by modules) */ void module_setmenu (DIALOG &dia, const char *menuid) { int n = modules.getnb(); for (int i=0; isetmenu (dia,menuid); } /* Probe the module for some update after configuration changes. */ int module_probe ( int state, // networking level 0, 1 or 2 // at which state are we checking int target) // idem, but the target of the general probe { int ret = 0; int n = modules.getnb(); bool simul = simul_ison()!=0; for (int i=0; iprobe (state,target,simul)==-1){ ret = -1; break; } } return 0; } /* Locate a module and let it produce some hints for the sysv init script Return LNCF_NOT_APPLICABLE if no module correspond */ int module_hint (const char *module) { int ret = LNCF_NOT_APPLICABLE; int n = modules.getnb(); for (int i=0; igetname(); if (mname != NULL && strcmp(module,mname)==0){ ret = m->probe (2,2,true); break; } } return ret; } /* Check if any module has something to do with this menu selection. Return != 0 if this menu event was managed by a module. */ int module_domenu (MENU_CONTEXT context, const char *key) { int ret = 0; int n = modules.getnb(); for (int i=0; igetname(); SSTRING file,about; file.setfromf ("about_%s",name); about.setfromf (MSG_U(T_ABOUTMODULE,"about module %s"),name); HELP_CONTEXT h1 (about.get(),name,file.get()); if (mod->domenu (context,key)){ ret = 1; break; } } return ret; } /* Check if any module has something to do with this menu selection. Return != 0 if this menu event was managed by a module. Variation allowing arbitrary menus to be enhanced. */ int module_domenu (const char *menuid, const char *key) { int ret = 0; int n = modules.getnb(); for (int i=0; idomenu (menuid,key)){ ret = 1; break; } } return ret; } /* Tell the different module to check their file permissions. Most of the time, those modules will call the function Return != 0 if there was any errors. */ int module_fixperm (bool boottime, bool silentflag) { int ret = 0; int n = modules.getnb(); for (int i=0; ifixperm (boottime,silentflag); } return ret; } /* Try to pass control to a module based on argv[0] Return LNCF_NOT_APPLICABLE if no module accept control. */ int module_execmain (int argc, char *argv[], bool standalone) { int ret = LNCF_NOT_APPLICABLE; int n = modules.getnb(); for (int i=0; iexecmain (argc, argv,standalone); if (code != LNCF_NOT_APPLICABLE){ ret = code; break; } } return ret; } /* Dispatch an HTML request to a module Return LNCF_NOT_APPLICABLE if no module accepted control. */ int module_dohtml(const char *key) { int ret = LNCF_NOT_APPLICABLE; int n = modules.getnb(); for (int i=0; idohtml (key); if (code != LNCF_NOT_APPLICABLE){ ret = code; break; } } if (ret == LNCF_NOT_APPLICABLE) html_ivldurl(); return ret; } /* Send a message to all modules. Return 0 if all ok Return LNCF_NOT_APPLICABLE if no module proceed the message Return != 0 if any error (returned by the modules) */ int module_sendmessage ( const char *msg, int argc, const char *argv[]) { int ret = 0; bool onemodule = false; int n = modules.getnb(); for (int i=0; imessage (msg,argc,argv); if (ok != LNCF_NOT_APPLICABLE){ ret |= ok; onemodule = true; } } return onemodule ? ret : LNCF_NOT_APPLICABLE; } int module_sendmessage (const char *msg) { return module_sendmessage (msg,0,NULL); } int module_sendmessage ( const MESSAGE_DEF &msg, int argc, const char *argv[]) { return module_sendmessage(msg.getmsg(),argc,argv); } int module_sendmessage (const MESSAGE_DEF &msg) { return module_sendmessage (msg,0,NULL); } /* Retrieve the command line usage of all modules */ void module_usage (SSTRINGS &tb) { int n = modules.getnb(); for (int i=0; iusage(tb); } } class MODULE_INFO: public ARRAY_OBJ{ public: char path[PATH_MAX+1]; char active; char original_active; // Original state of the module // so we can write proper information // in conf.linuxconf /*~PROTOBEG~ MODULE_INFO */ public: MODULE_INFO (bool _active, const char *name); MODULE_INFO (void); void checkdep (void); int locate (char realpath[PATH_MAX]); int write (void); /*~PROTOEND~ MODULE_INFO */ }; static const char K_MODULE[]="module"; static const char K_LIST[]="list"; PUBLIC MODULE_INFO::MODULE_INFO(bool _active, const char *name) { strcpy (path,name); active = _active ? 1 : 0; original_active = _active; } PUBLIC MODULE_INFO::MODULE_INFO() { path[0] = '\0'; active = 1; original_active = 0; } PUBLIC int MODULE_INFO::write () { if (path[0] != '\0' && (original_active != 0 || active != 0)){ char buf[PATH_MAX+4]; sprintf (buf,"%d %s",active,path); linuxconf_add (K_MODULE,K_LIST,buf); } return 0; } /* Locate a module based on the version of linuxconf Return -1 if the module can't be found. realpath will contain the path linuxconf was looking for. Return the subrevision number. */ int module_locate( const char *basepath, // base path of the module, without extension char *realpath) // Complete path as located { int ret = -1; struct stat st; char tmppath[PATH_MAX]; if (strchr(basepath,'/')==NULL){ /* #Specification: module / path / default path If a module is define only with its name, linuxconf assume it is located in /usr/lib/linuxconf/lib */ snprintf (tmppath,sizeof(tmppath)-1,"%s/modules/%s",USR_LIB_LINUXCONF,basepath); basepath = tmppath; } if (stat(basepath,&st)!=-1 && (st.st_mode & S_IXUSR)){ ret = 0; strcpy (realpath,basepath); }else{ /* #Specification: module / selection Linuxconf will pick the module with the largest version where a version is defined as x.y.z.w and where the missing suffixes are replaced by 0. So # 1.9 is 1.9.0.0 # for example. */ long maxrev = -1; int norev = -1; char trypath[PATH_MAX]; int lentry = snprintf (trypath,sizeof(trypath)-1,"%s.so.",basepath); SSTRINGS lst; int nb = dir_getlist_p (trypath,lst); for (int i=0; iget(); long rev = 0; long fact = 100*100*100; const char *ext = p + lentry; bool err = false; while (1){ if (isdigit(ext[0])){ rev += atoi(ext) * fact; fact /= 100; ext = str_skipdig(ext); if (ext[0] != '.'){ if (ext[0] != '\0') err = true; break; } ext++; }else{ err = true; break; } } if (!err && rev > maxrev){ maxrev = rev; norev = i; } } if (norev != -1){ strcpy (realpath,lst.getitem(norev)->get()); ret = 0; } } return ret; } /* Load the libmodules required by this module */ PUBLIC void MODULE_INFO::checkdep() { char dep[PATH_MAX]; snprintf (dep,sizeof(dep)-1,"/usr/lib/linuxconf/modules/%s.dep",path); FILE *fin = fopen (dep,"r"); if (fin != NULL){ char buf[100]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ strip_end(buf); if (buf[0] != '\0') new LIBMODULE(buf); } fclose (fin); } } /* Locate a module based on the version of linuxconf Return -1 if the module can't be found. realpath will contain the path linuxconf was looking for. Return the subrevision number. */ PUBLIC int MODULE_INFO::locate (char realpath[PATH_MAX]) { /* #Specification: modules / revision management Linuxconf's modules follow a strict numbering scheme to avoid strange incompatibilities. For each linuxconf version, linuxconf expect a module using the same version number. Module may use a seperate number to differentiate internal releases. Linuxconf will always used the highest internal release. For example, given a module "/var/project/dummy", linuxconf 1.6 will look for /var/project/dummy.so.1.6.0. If it exist, linuxconf will look for /var/project/dummy.so.1.6.1 and so on. It will use the highest found. To give some flexibility to the user, linuxconf allows one to specify the full path of the module. So linuxconf first try to open this file. If it exist, it is believe to be the module. If not, the extension .so.LINUXCONF_REV.SUBREV is tried. The idea is to let someone say "Use /var/project/dummy.so.1.7.4" just to see if it is better than 1.7.5 for example. */ return module_locate (path,realpath); } class MODULE_INFOS: public ARRAY{ /*~PROTOBEG~ MODULE_INFOS */ public: MODULE_INFOS (void); MODULE_INFO *getitem (int no); void read_all (void); void setone (const char *path, bool force, bool enabled); void unsetone (const char *path); int write (void); /*~PROTOEND~ MODULE_INFOS */ }; PUBLIC MODULE_INFO *MODULE_INFOS::getitem(int no) { return (MODULE_INFO *)ARRAY::getitem(no); } PUBLIC MODULE_INFOS::MODULE_INFOS() { SSTRINGS tb; /* scan /etc/conf.linuxconf for modules */ int n = linuxconf_getall (K_MODULE,K_LIST,tb,0); for (int i=0; iget(); const char *name = str_skip(s+1); add (new MODULE_INFO(s[0] != '0',name)); } } /* Add information about all available module in /usr/lib/linuxconf/modules */ PUBLIC void MODULE_INFOS::read_all() { char modpath[PATH_MAX]; snprintf(modpath,sizeof(modpath),"%s/modules",USR_LIB_LINUXCONF); SSTRINGS dir; int x = dir_getlist(modpath,dir); /* strip ".so.X.X.X" from module names and add "0 " to beginning of it.*/ for(int i = 0; igetlen(); char b[len+1]; s->copy (b); char *pt = strchr(b,'.'); if (pt != NULL) *pt = '\0'; s->setfrom (b); } for (int i=0; iget(); bool state = false; for(int a=0 ; apath,fromdir) == 0) { state = true; break; } } if(!state) add (new MODULE_INFO(false,fromdir)); } } PUBLIC int MODULE_INFOS::write () { linuxconf_removeall (K_MODULE,K_LIST); for (int i=0; iwrite(); return linuxconf_save(); } static void module_addfields ( MODULE_INFOS &infos, DIALOG &dia, int f, PRIVATE_MESSAGE &msg) { /* #Specification: modules / description The module description is stored in a file holding its name in /usr/lib/linuxconf/descriptions/LANG/. Each file there should only have a single short line. We are reading only the first line to build the dialog anyway. */ MODULE_INFO *in = infos.getitem(f); SSTRING path; path.setfromf("%s/descriptions/%s/%s" ,USR_LIB_LINUXCONF,linuxconf_getlang(),in->path); FILE *fin = fopen (path.get(),"r"); if (fin == NULL){ /* Translation missing, trying to open english descriptions */ path.setfromf("%s/descriptions/eng/%s" ,USR_LIB_LINUXCONF,in->path); fin = fopen(path.get(),"r"); } SSTRING descr; if (fin != NULL){ char buf[100]; buf[0] = '\0'; fgets (buf,sizeof(buf)-1,fin); buf[sizeof(buf)-1] = '\0'; fclose (fin); strip_end (buf); descr.setfrom (buf); } dia.newf_chk(in->path,in->active,descr.get()); dia.set_helpdia(msg); } static int cmp_by_name (const ARRAY_OBJ *o1, const ARRAY_OBJ *o2) { const MODULE_INFO *i1 = (MODULE_INFO*)o1; const MODULE_INFO *i2 = (MODULE_INFO*)o2; return strcasecmp(i1->path,i2->path); } void module_config() { MODULE_INFOS infos; infos.read_all(); infos.sort(cmp_by_name); int nof = 0; DIALOG dia; int i; int n = infos.getnb(); PRIVATE_MESSAGE tbmsg[n]; for (i=0; ipath); HELP_FILE h (in->path,about.get()); dialog_showhelp (h); break; } } }else if (code == MENU_ACCEPT){ for (i=0; iactive && in->path[0] != '\0' && in->locate(path)==-1){ xconf_error (MSG_U(E_MODNOPATH ,"Module %s does not exist\n"),in->path); nof = i*2; break; } } if (i == n){ infos.write(); /* #Specification: modules / activation Whenever we accept the change in the module list, linuxconf will try to load and activate a module. Doing a dlopen on a module twice is not a problem. Unloading a module is not really possible during a session, so removal or desactivation of a module will take effect at the next session. */ module_load(); break; } } } } static SSTRINGS tbloaded; void module_loadcheck(const char *path) { if (tbloaded.lookup(path)!= -1) return; /* #Specification: module / dlopen / RTLD_LAZY Linuxconf is loading modules with dlopen flag RTLD_LAZY. Some modules are using dlopen() internally and require that their name space become visible to the sub-modules. As far as we know, this is only needed for exceptional modules (redhat and conectiva using the libpam library), but might turn out to the useful for other in the future (especially module providing bindings to various interpreted language). The case known is when a module is linked with a special library which itself does dlopen() at some point. The libpam library does that. With RTLD_LAZY flag, the various symbols of the module are not exported, so the "sub-modules" can't be loaded (the various pam_xxx.so modules). This RTLD_GLOBAL flag has two disadvantages (maybe) # -It is probably a little slower (as it imply RTLD_NOW). -Two modules may not defined the same symbol twice. This might be annoying as two independant modules author may define too global function with the same name and the problem will only occurs when both modules are used at the same time. Further the behavior of the dynamic linker is pretty weird: It does not complain but screw things... # So for now, we are using RTLD_LAZY for all module except the redhat and conectiva modules. Note that the exception is handled in linuxconf code. Module requiring RTLD_GLOBAL are probably exceptional anyway. */ bool need_global = strstr (path,"/redhat.so") != NULL || strstr (path,"/conectiva.so") != NULL || strstr (path,"/suse.so") != NULL || strstr (path,"/debian.so") != NULL || strstr (path,"/pythonmod.so") != NULL; void *handle = dlopen (path ,need_global ? RTLD_GLOBAL|RTLD_NOW : RTLD_LAZY); if (handle == NULL){ xconf_error (MSG_U(E_LOADMOD ,"Can't load module\n" "\t%s\n" "%s"),path,dlerror()); }else{ /* #Specification: module / compatibility A module is compiled using some APIs in linuxconf. To make sure a given module is compatible with a given API revision, each module defines an integer global variable which contains the API version. This is done with the MODULE_DEFINE_VERSION macro. Each module must defined a unique symbol so RTLD_GLOBAL may be used. So symbol is built by appending the module name to MODULE_SYMBOL_VERSION. */ // We extract the module name from the path to compose // the special symbol name. char sym[200]; // Will contain the built symbol { const char *pt = strrchr(path,'/'); if (pt == NULL){ pt = path; }else{ pt++; } snprintf (sym,sizeof(sym)-1,"%s_%s",MODULE_SYMBOL_VERSION,pt); char *pt2 = strchr(sym,'.'); if (pt2 != NULL) *pt2 = '\0'; } int *pt = (int*)dlsym (handle,sym); if (pt == NULL || *pt != MODULE_API_VERSION){ xconf_error (MSG_U(E_COMPATMOD ,"Incompatible module %s\n" "get a new one or recompile it against a recent linuxconf-devel package\n" "\n" "Expect API revision %d, got %d") ,path,MODULE_API_VERSION,*pt); LINUXCONF_MODULE *mod = modules.getitem(modules.getnb()-1); modules.remove (mod); }else{ tbloaded.add (new SSTRING(path)); } } } /* Get the path of all loaded modules. It returns a SSTRINGS pointer. Do not delete it. */ const SSTRINGS *module_getlist() { return &tbloaded; } /* Load all the linuxconf modules. If a module is already loaded, it won't be loaded twice. So this function may be called several time, generally after a module_setone() call. */ void module_load () { #ifndef LINUXCONF_AOUT MODULE_INFOS infos; infos.sort(cmp_by_name); for (int i=0; iactive){ in->checkdep(); char path[PATH_MAX]; if (in->locate(path)==-1){ fprintf (stderr,MSG_R(E_MODNOPATH),in->path); }else{ message_def_setcurmodule (in->path); module_loadcheck(path); message_def_setcurmodule (NULL); } } } #endif } void module_loaddistmod() { const char *dist = linuxconf_getdistdir(); message_def_setcurmodule(dist); char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"%s/%s/%s.so.%s.%d",USR_LIB_LINUXCONF,dist,dist ,LINUXCONF_REVISION,LINUXCONF_SUBREVISION); if (file_exist (path)){ module_loadcheck (path); linuxconf_forget(); } message_def_setcurmodule (NULL); } /* Add one module to the list. If the module is already in the list, let it there without affecting its enabled flag. */ PUBLIC void MODULE_INFOS::setone (const char *path, bool force, bool enabled) { bool found = false; for (int i=0; ipath,path)==0){ found = true; if (force){ in->active = enabled ? 1 : 0; write(); } break; } } if (!found){ add (new MODULE_INFO (true,path)); write(); } } /* Remove a module from the list. */ PUBLIC void MODULE_INFOS::unsetone (const char *path) { bool found = false; for (int i=0; ipath,path)==0){ found = true; remove_del (in); break; } } if (found){ write(); } } /* Add a module to the list of active modules. If the module is already there, its status enabled is not changed unless force==true. */ void module_setone (const char *path, bool force, bool enabled) { MODULE_INFOS infos; infos.setone (path,force,enabled); } /* Add a module to the list of active modules */ void module_setone (const char *path) { module_setone (path,false,false); } /* Remove a module from the list of active modules */ void module_unsetone (const char *path) { MODULE_INFOS infos; infos.unsetone (path); } /* Return true if a module is enabled */ bool module_is_enabled(const char *path) { bool ret = false; MODULE_INFOS infos; for (int i=0; ipath,path)==0){ ret = in->active != 0; break; } } return ret; } /* Send a message to a the distribution specific module so it can check if a given package is installed. logical_name is something like "popserver" "dhcpserver" and so on. The distribution module translate this name according to its packaging and check if it is installed. If not, it propose to install it. For example, the redhat module will check is imap is installed if popserver is requested. This strategy should help administrator get it right the first time. This opens a new concept: What is coming first, the admin tool, or the service. It seems the admin tool is a good ambassador here. The function returns 1 if the package was installed, 0 otherwise (maybe nothing was done or the package was already installed). */ int module_requestpkg (const char *logical_name) { int ret = 0; if (dialog_mode != DIALOG_TREE){ const char *tb[]={logical_name}; ret = module_sendmessage ("request_package",1,tb); } return ret; }