Previous Next Table of Contents

4. Creating the module code

From now on, we will describe the mrtg module.

4.1 Creating the mrtg.cc file

A module hook itself in Linuxconf by deriving a new class from the base class LINUXCONF_MODULE. We define this in a .cc file with the same name as the sub-directory. This is only a convention. Here this is mrtg.cc

The include files

The mrtg.cc file starts with these directives.

        #pragma implementation
        #include <stdio.h>
        #include <translat.h>
        #include "mrtg.h"
        #include "mrtg.m"
        

The pragma is there to tell GCC that this is the mrtg.cc file corresponding to the mrtg.h file and as such, some hidden object should be generated.

The rest are standard includes.

Some magic definition

The following line in mrtg.cc defines a versioning keys used by Linuxconf to insure that the module is compatible with the current version of Linuxconf. Just copy this line.

        MODULE_DEFINE_VERSION;
        

Defining the MODULE_MRTG constructor

Next we define the constructor for the MODULE_MRTG object which is derived from LINUXCONF_MODULE base class.

        PUBLIC MODULE_MRTG::MODULE_MRTG()
        {
            linuxconf_loadmsg ("mrtg",PACKAGE_REV);
        }
        

Here the constructor does only the minimum. It loads the messages dictionary (translation system). The constructor may do anything specific to initialize the module. There is generally only one instance of the module object for a given module, so this constructor is called only once.

Defining the MODULE_MRTG destructor

Most module don't need a destructor. Use it only if you have something specific to do. Linuxconf only call the destructor at exit time. This explain why most module don't need one (the memory cleanup is done by the OS).

Inserting the module in some menu

A module may insert itself in various menu of Linuxconf. Two member functions must be overridden for this to operate. The first one, setmenu(), allows the module to effectively add entries in a specific menu. The second one, domenu(), lets the module gain control when the menu entry has been selected by the user.

Beware that a module may choose to insert itself in more than one Linuxconf menu. It may also insert more than one entry per menu.

The various menu context

The MENU_CONTEXT enumeration, defined in dialog/dialog_def.h (sub-included in mrtg.h, allowing normal compilation) identify the various menu which may be enhanced by Linuxconf. This list may grow in the future, so if you feel your module should insert itself elsewhere, please ask. Here is the list of MENU_CONTEXT currently defined.

        MENU_NETWORK_CLIENT,    // Client section of the network menu
        MENU_NETWORK_SERVER,    // Server section of the network menu
        MENU_NETWORK_MISC,      // Misc section of the network menu
        MENU_MAIN_CONFIG,       // Config section of the main linuxconf menu
        MENU_MAIN_CONTROL,      // Control section of the main linuxconf menu
        MENU_CONTROL,           // Control panel
        MENU_NETWORK_BOOT,      // Boot services section of the network menu
        

The setmenu() function

Here is the code for the setmenu() and domenu() function. Both share the same logic to identify if they are called for the proper menu context. Linuxconf blindly calls all modules to let them insert themselves in the various menu. If a module fail to check the context argument, it will appear uselessly in many menus.

The variable keymenu is initialized in the setmenu() function. The translation system does not allow the definition of messages out of function scope. setmenu() is always called before domenu() is called anyway.

        static const char *keymenu=NULL;

        PUBLIC void MODULE_MRTG::setmenu (
            DIALOG &dia,
            MENU_CONTEXT context)
        {
            if (context == MENU_NETWORK_MISC){
                keymenu = MSG_U(M_MRTG,"Multi Router Traffic Grapher (MRTG)");
                dia.new_menuitem ("mrtg","",keymenu);
            }
        }
        

The DIALOG object is the key of Linuxconf's user interface. See http://www.solucorp.qc.ca/linuxconf/tech/api/index.html to learn more.

DIALOG::new_menuitem simply passes the name of the icon file associated with this menu entry. The icon file is simply mrtg. An mrtg.xpm file must be provided as well as an mrtg.gif file. Both must be installed in $(LIB_LINUXCONF)/images in the install script. This is handled in stdmod.mak if those icon files exist.

The domenu() function

        PUBLIC int MODULE_MRTG::domenu (
            MENU_CONTEXT context,
            const char *key)
        {
            if (context == MENU_NETWORK_SERVER){
                if (key == keymenu){
                    mrtg_edit();
                }
            }
            return 0;
        }
        

domenu() simply calls mrtg_edit() if called for the proper menu context, and the proper entry was selected. The mrtg_edit() function is presented later in this document.

Gaining control at probe time

Linuxconf has a key feature which make it very different from all other administration system: It can probe about everything in the system and compute the proper course of action to bring the system state in sync with its configuration. (No need to reboot, Linuxconf knows how to make effective about any configuration changes).

A module may redefine the LINUXCONF_MODULE::probe() function and gain control. This function will be called several time at various stage of the probe done by Linuxconf. The probe function is optional. It returns 0 if all is OK and -1 if any errors were detected.

The probe() function

Here is the prototype of the probe() function

        PUBLIC int MODULE_MRTG::probe (
            int level,
            int target,
            bool simul) // Do it or only print the actions
        {
            // this service must be configured only
            // in network level 1 or higher
            if (target == 0){
                // Do something to turn off this service
                        // if needed
            }if (level == 1){ // Client mode
                // Do something to enable the service
                // if needed
            }
                return 0;
        }
        

The level argument indicates what is the current level of the probe. Linuxconf defines 3 network levels:

The target argument indicates what is the current operation level of the workstation. At any given time, a workstation is either not running any network services or only client or only server. This is a decision the user/administrator has taken at boot time or using the control panel. This is not related to the fact that the machine is a server or not. This is only an operation mode.

Some service must be configured differently based on the current operation mode (target). One special case happens when the target is lower then the level. This case means that some services may have to be removed.

Here is a list of things a module may do. Not that most often these activity are managed by other Linuxconf objects so most modules do nothing here. But here is a list, anyway.

The complexity of the probe() function

Beware that the probe() function is not called probe for nothing: Its goal is to extract some configuration state and compare that to the operational state of a given service. If they match then it does nothing, if not, it must compute the proper set of commands/operations to make the configuration effective.

The probe() function is effectively called twice!!!

The probe() function is called at several time during the global probe done by Linuxconf. Most modules using a probe() function will only do something for the proper level. So they are effectively called once. Well, not really, they are called for the proper level twice.

This is a very important feature of Linuxconf: It must be able to present to the administrator the list of things needed to put the machine in sync with its configuration. The administrator must be able to preview that.

So basically, the probe() function must be able to compute the action and do them only if called in the proper mode (in simulation mode or not).

The net_prtlog() function

The net_prtlog() function allows the probe() function to report what it will do. This is very important. In simulation mode Linuxconf collect the output of this function to find out if there is some work to do and prompt the user.

The prototype of this function is

        void net_prtlong (int type, const char *ctl, ...);
        

So this is basically a printf-like function. The various type are defined in the netconf_def.h include files. Here they are.

        #define NETLOG_REQ      1    // Required output to netconf.log
        #define NETLOG_ERR      2    // Error messages from the commands
        #define NETLOG_OUT      4    // Stdout from the commands
        #define NETLOG_VERB     8    // Informative stuff produced by linuxconf
        #define NETLOG_CMD      16   // Command line or action
        #define NETLOG_TITLE    32   // Group title
        #define NETLOG_SECTION  64   // Section title
        #define NETLOG_DONTDO   128  // Things which won't be done
        #define NETLOG_WHY      256  // Why linuxconf is doing something
        

The netconf_system_if() function

The netconf_system_if() function is really the function to use when a module need to execute some commands. This function works a little like the system() standard C function. Here is the prototype.

        int netconf_system_if(const char *command, const char *args);
        

This function does a lot. You really need to use it. Here are the features:

Gaining control from the command line

A module may define its own set of command line options. A module will gain control when linuxconf is executed using

        linuxconf --modulemain modulename [ args ... ]
        

Or if a symbolic link is set up like this

        ln -sf /bin/linuxconf /bin/modulename
        /bin/modulename [ args ... ]
        

The execmain() function

The execmain() function is optional. It operates much like the main() function of any C/C++ program. As such it receives an argument count and an argument vector. The element 0 of the argument vector is the name by which Linuxconf was called (or the argument of the --modulemain option). A module must test this name to see if the call related to itself. If the module does not recognize the name, it simply returns LCNF_NOT_APPLICABLE, telling Linuxconf to try another module. Otherwise, the module will try to parse the arguments and proceed accordingly. The value returned by the function is simply return to the calling process by Linuxconf allowing construct like this to operate normally.

        if linuxconf --modulemain modulename args
        then
            echo succeeded
        else
            echo failed
        fi
        

Gaining control from the HTML interface

A module may define its own entry point in the HTML hierarchy of Linuxconf. While a module may insert itself in the various menus of Linuxconf and as such will appear in the HTML interface, a module may choose to appear directly. The user does not see the other Linuxconf menus. The user must use a special URL to access the module. The special URL goes like this

        http://the_workstation:98/htmlmod:key:...
        

The dohtml() function

The keyword key is passed to the dohtml() function. If the key does not match, the function return LCNF_NOT_APPLICABLE and Linuxconf simply dispatch the call to the next module.

If the key matches, then the module will simply create a dialog or a menu using the same API normally used elsewhere.

4.2 The interactive part of the module

Most module will have the following components:

Configuration file definition

When a module access a configuration file or a resource file, it can do that using fopen but using a CONFIG_FILE object is much more appropriate. The CONFIG_FILE provides the following features:

For all these reason, using CONFIG_FILE is not really an option :-)

Here is the declaration of the mrtg.conf config file

        static CONFIG_FILE f_cfg ("/home/httpd/html/mrtg/mrtg.cfg"
            ,help_mrtg,CONFIGF_MANAGED|CONFIGF_OPTIONAL
            ,"root","root",0644
            ,subsys_mrtg);
        

The API of the CONFIG_FILE class is presented in ../api/config_file/config_file.i.html.

Is the path hard coded?

The declaration of the variable f_cfg above looks like the path of the file mrtg.conf is hard-coded. Not really. This path must be seen as two things

Help screen definition

HELP_FILE objects are needed to define CONFIG_FILE and also to edit dialog. Defining an object is easy. One must simply specify the name of the base directory, which is normally the module name and then the name of the help file without extension. Here is how it is done in the mrtg module.

        static HELP_FILE help_mrtg ("mrtg","mrtg");
        

Writing help screens

Help screens are written in sgml-tools, using the linuxdoc DTD. Check out ../../translat-helps to learn more.

User privileges

The mrtg module defines one privilege. This is a little overkill but is done as a demonstration. This privilege may be granted to any user account (individually) and allow this user to configure mrtg. Here is the privilege definition.

        static PRIVILEGE p_mrtg ("mrtgadmin"
            ,P_MSG_U(T_PRIVMRTG,"MRTG administrator")
            ,P_MSG_U(T_PMISC,"9-Miscellaneous"));
        

mrtgadmin is the key identifying the privilege. The second argument is the title of the privilege which will appear automagically in the user account dialog. The third is the category in which this privilege will appear in the dialog. The 9- is a sort prefix. It won't appear in the dialog, but will make sure the category miscellaneous is at the bottom of the dialog.

A new facility will be create to better organized the naming of the categories.

The P_MSG_U macro is used instead of the normal MSG_U macro to declare translatable text. This is need because the p_mrtg object is initialized at program startup, well before the message dictionary is initialized. The P_MSG_U macro declare an indirect object allowing the PRIVILEGE class to extract the message later.

Parsers and objects

There is currently no general purpose framework to parse configuration file in Linuxconf. There are various help functions you may want to check. Here is the sample code from the mrtg module

        FILE *fin = f_cfg.fopen ("r");
        if (fin != NULL){
            char buf[1000];
            SSTRING comments;
            while (fgets_comments (buf,sizeof(buf)-1,fin,comments,'#')!=NULL){
                strip_end (buf);
                char keyw[200];
                char id[200];
                char value[PATH_MAX+200];
                if (mrtg_split (buf,keyw,id,value)==-1){
                    xconf_error (MSG_U(E_IVLDDIREC,"Invalid directive: %s\n")
                        ,buf);
                }else if (strcmp(keyw,"WorkDir")==0){
                    workdir_comment.setfrom (comments);
                    workdir.setfrom (value);
                }else if (id[0] != '\0'){
                    MRTG_CONF *one = getitem (id);
                    if (one == NULL){
                        one = new MRTG_CONF (id);
                        add (one);
                    }
                    if (strcmp(keyw,"Target")==0){
                        one->target_comment.setfrom (comments);
                        one->target.setfrom (value);
                    }else if (strcmp(keyw,"MaxBytes")==0){
                        one->maxbytes_comment.setfrom (comments);
                        one->maxbytes = atoi (value);
                    }else if (strcmp(keyw,"Title")==0){
                        one->title_comment.setfrom (comments);
                        one->title.setfrom (value);
                    }else if (strcmp(keyw,"PageTop")==0){
                        one->pagetop_comment.setfrom (comments);
                        one->pagetop.setfrom (value);
                    }else{
                        xconf_error (MSG_R(E_IVLDDIREC),buf);
                    }
                }else{
                    xconf_error (MSG_R(E_IVLDDIREC),buf);
                }
                comments.setfrom("");
            }
            last_comment.setfrom (comments);
            fclose (fin);
        }
        

The key elements of this code sample are:


Previous Next Table of Contents