Next Previous Contents

17. module APIs

#Specification: module apis / principles ([module_api.cc,1])

It is sometime useful for a module to call another module. There is a problem with that as the called module may be missing (not always available). So caller modules need a way to find out if a module is available and then a reliable and simple way to reach various functions inside that module. More generally, some caller code may need to reach some API (set of function) located in some unknown module. For example, a given API may be provided by competing modules. So ultimatly, a caller is not concerned about the availability of another module but simply about the availability of a given API. This is what we have in Linuxconf.

#Specification: module apis / how to use them ([module_api.cc,27])

Here is a code sample showing how a client may use a module API: (The first module to define an API was dnsconf and the first client was the dhcpd module)

        #include <module_apis/dnsconf_api.h>
        ,
        DNSCONF_API *api = dnsconf_api_init("dhcpd");
        if (api != NULL){
            api->setfromip ("host.domain.com","192.168.3.1");
            dnsconf_api_end (api);
        }
The argument passed to dnsconf_api_init() is a string identifying the caller code. It is used to signal incompatibility between the API version requested by the client (encoded in the dnsconf_api_init() call) and the version provided by another module. This string is usually the name of the client module. As a convention, an API is defined by two headers. Those headers are located in the subdirectory modules/module_apis/ of the Linuxconf source tree. They are also located in /usr/include/linuxconf/module_apis/ when using the linuxconf-devel package. Note that to access those headers, one only needs the following construct:
        #include <module_apis/foo_api.h>
So for an API named foo, there are the files foo_api.h and foo_apidef.h. The foo_apidef.h provides the definition for the struct FOO_API. The foo_api.h sub-include foo_apidef.h and defines the 3 static functions foo_api_init(), foo_api_end() and foo_api_available. Most client code will generally only include the foo_api.h file. The other header is used by client passing FOO_API pointer around. The foo_api_available() is a helper function. It returns true if the API is available, false if not. Client code must cope with the unavailability of an API. There is no need to use the foo_api_available() prior to call foo_api_init(). The later will return NULL if the API is not available. The foo_api_available is used in place where we must do something different if an API is available, but we do not need the API at this time. For example, the code creating a menu may add some option if an API is available.

#Specification: module apis / how to define them ([module_api.cc,77])

To define a new API, we select a name and then creates two headers. Here is header for the dnsconf API.

        #ifndef DNSCONF_API_H
        #define DNSCONF_API_H
        #include <module_apis/dnsconf_apidef.h>
        static const char DNSCONF_API_KEY[]="dnsconf";
        static const char DNSCONF_API_REV=1;
        inline DNSCONF_API *dnsconf_api_init(const char *client)
        {
            return (DNSCONF_API*)module_get_api (DNSCONF_API_KEY,DNSCONF_API_REV,client);
        }
        inline void dnsconf_api_end(DNSCONF_API *api)
        {
            module_release_api (DNSCONF_API_KEY,(void*)api);
        }
        inline bool dnsconf_api_available(const char *client)
        {
            return module_api_available (DNSCONF_API_KEY,DNSCONF_API_REV,client);
        }
        #endif
As you see, the name of the API as well as its revision (1) are define in the module_get_api() call. An exact revision match is required. Linuxconf will signal any incompatibilities. The "client" string is only used to identify the client in case an error message must be generated. Put any string here, such as the name of the calling module. Note also that the module_get_api() and module_release_api() function are never directly called by client code. The dnsconf_apidef.h looks like this:
        #ifndef DNSCONF_APIDEF_H
        #define DNSCONF_APIDEF_H
        class DNS;
        class IP_ADDRS;
        class SSTRINGS;
        struct DNSCONF_API{
            int (*set) (const char *host, const char *tbip[], int nbip);
            int (*setfromip) (const char *host, const char *ip);
            int (*setcname) (const char *host,const char *nickname);
            int (*setns) (const char *domain, const char *tbns[], int nbns);
            int (*setmx) (const char *domain, const char *tbmx[], int nbmx);
            int (*setfromrange) (const char *host, const char *range);
            int (*unset) (const char *host);
            int (*geta) (const char *host, IP_ADDRS &tbip);
            int (*getns) (const char *host, SSTRINGS &tbns);
            int (*getmx) (const char *host, SSTRINGS &tbmx);
            int (*write) ();
            DNS *dns;
        };
        #endif
As you see, it is simply a struct definition with the necessary class forward definition. Note that an API may contain some data (DNS pointer here) usable by the server. It is a good idea to place this stuff at the end of the struct so you are free to add fields as needed. API struct are allocated by the server, not the client.

#Specification: module apis / the server side ([module_api.cc,144])

An API server must register its API at startup. This is generally done in the LINUXCONF_MODULE constructor (derived by the module). The module_register_api() function is used. It must provide the following information:

Here is the code for the dnsconf module.

        PUBLIC MODULE_dnsconf::MODULE_dnsconf()
            : LINUXCONF_MODULE("dnsconf")
        {
            linuxconf_loadmsg ("dnsconf",PACKAGE_REV);
            module_register_api ("dnsconf",1,dnsconf_api_get
                ,dnsconf_api_release);
        }
        void *dnsconf_api_get ()
        {
            DNSCONF_API *api = new DNSCONF_API;
            api->set = dnsconf_api_set;
            api->setfromip = dnsconf_api_setfromip;
            .
            .
            return api;
        }
        void dnsconf_api_release (void *api)
        {
            delete (DNSCONF_API*)api;
        }
An API is just a collection of normal C++ non-member functions.

#Specification: module apis / APIs as object ([module_api.cc,188])

An API is a collection of function. It does not represent any state. You ask for an API object and you can use the function pointer in it. Sometime it is useful to see an API as an object with a given private state. A server is free to add some stuff at the end of the function list. Most often, it will add a pointer to an opaque (private) data type. When calling one API function, the called server does not receive (unlike C++ member function) a pointer to the API object, so do not have access to this private data structure. The solution to this problem is simply to add the API object as part of the function definition. For example, one can define the following API:

        struct FOO_API{
            void (*load)(FOO_API *a, const char *file);
            void (*edit)(FOO_API *a);
            void (*save)(FOO_API *a);
            class FOO_PRIVATE *p;
        };
The function foo_api_get() and foo_api_release() are free to provide a new instance of FOO_API to every new caller.

#Specification: module apis / multiple providers ([module_api.cc,338])

In general, if several modules implement the same API, these modules are not used together. For example, the managerpm module defines the PACKAGE_API and one they the module managedeb will provide the same. But the module won't be used at the same time. In some case, several module may implement the same API concurently. The client has to be aware of that. Instead of using function like APINAME_api_init, it is using APINAME_apis_init. For example the firewall module expect many module will provide interface definition. So it does something like that.

        FWINFO_API *tbapi[MAX_API_PROVIDERS];
        int nb = fwinfo_get_apis ("ipfwrule",tb);
        for (int i=0; i<nb; i++){
            tbapi[i]->getinfo (...)
        }
        fwinfo_release_apis (tb,nb);
This solution competes with the message (see module_sendmessage()) facility. The message mecanism is simpler to define, but is not strict: A bunch of strings are used as parameters.
Next Previous Contents