/* #Specification: module apis / principles
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.
- APIs are uniquely identified by a name.
- APIs do have version number.
- APIs are defined in indenpendant header files.
- Several modules may implement one API.
- A module may define as many APIs as needed.
- API are available to other modules as well as Linuxconf's core.
- An API is not an object nor it manages (by definition) an instance
of an object. But it is possible to use them this way.
*/
/* #Specification: module apis / how to use them
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
,
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
#
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
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
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
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:
- Name of the API.
- Revision of the API.
- Initialisation function.
- Termination function.
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
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.
*/
#include
#include
#include "misc.h"
#include "misc.m"
// To make sure this is linked in
void module_api_required(){}
class MODULE_API: public ARRAY_OBJ{
public:
void *(*get)();
void (*release)(void *api);
/*~PROTOBEG~ MODULE_API */
public:
MODULE_API (void * (*_fctget)(),
void (*_fctrelease)(void *));
/*~PROTOEND~ MODULE_API */
};
PUBLIC MODULE_API::MODULE_API(
void *(*_fctget)(),
void (*_fctrelease)(void *))
{
get = _fctget;
release = _fctrelease;
}
typedef ARRAY_OBJS MODULE_APIS;
#if 0
class MODULE_APIS: public ARRAY{
/*~PROTOBEG~ MODULE_APIS */
/*~PROTOEND~ MODULE_APIS */
};
PUBLIC MODULE_API* MODULE_APIS::getitem (int no) const
{
return (MODULE_API*)ARRAY::getitem (no);
}
#endif
class MODULE_APIREF: public ARRAY_OBJ{
public:
char *name;
int version;
bool signal; // Did we signal an API incompatibility once ?
MODULE_APIS tb; // Various providers of the API (generally only one)
/*~PROTOBEG~ MODULE_APIREF */
public:
MODULE_APIREF (const char *_apiname,
int _version);
void add (void * (*_fctget)(),
void (*_fctrelease)(void *));
~MODULE_APIREF (void);
/*~PROTOEND~ MODULE_APIREF */
};
PUBLIC MODULE_APIREF::MODULE_APIREF(
const char *_apiname,
int _version)
{
name = strdup(_apiname);
version = _version;
signal = false;
}
PUBLIC MODULE_APIREF::~MODULE_APIREF()
{
free (name);
}
PUBLIC void MODULE_APIREF::add(
void *(*_fctget)(),
void (*_fctrelease)(void *))
{
tb.add (new MODULE_API (_fctget,_fctrelease));
}
static ARRAY_OBJS tb;
/*
Record the provider of an API.
This function is generally called at module initialisation time.
*/
void module_register_api (
const char *apiname,
int version,
void *(*fctget)(), // This return a pointer to the API struct
// The server is free to return a newly
// allocated struct or the same for every call
void (*fctrelease)(void *)) // The release function does whatever it
// wants to clean the API struct
{
MODULE_APIREF *found = NULL;
for (int i=0; iname,apiname)==0){
found = ref;
break;
}
}
if (found == NULL){
found = new MODULE_APIREF (apiname,version);
tb.add (found);
}
if (found->version != version){
xconf_error (MSG_U(E_IVLDMULTIAPI
,"Incompatible API version: %s %d"),apiname,version);
}else{
found->add (fctget,fctrelease);
}
}
/* #Specification: module apis / multiple providers
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; igetinfo (...)
}
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.
*/
/*
Get all the API objects for a given API name.
Return the number of API objects loaded in tb[].
*/
int module_get_apis (
const char *apiname,
int version,
const char *client, // Some message identifying the caller
void *tbapi[MAX_API_PROVIDERS])
{
int ret = 0;
int n = tb.getnb();
for (int i=0; iname,apiname)==0){
if (ref->version != version){
if (!ref->signal){
// Signal the error only once
xconf_error (MSG_U(E_APIVERSION
,"Incompatible module API \"%s\"\n"
"\"%s\" requested version %d.\n"
"Version %d is provided.")
,apiname,client,version,ref->version);
ref->signal = true;
}
}else{
for (int r=0; rtb.getnb(); r++){
MODULE_API *ap = ref->tb.getitem(r);
tbapi[ret++] = ap->get();
}
}
break;
}
}
return ret;
}
/*
Release an API object
*/
void module_release_apis (
const char *apiname,
void *tbapi[],
int nbapi)
{
if (nbapi > 0){
int n = tb.getnb();
for (int i=0; iname,apiname)==0){
for (int r=0; rtb.getnb(); r++){
MODULE_API *ap = ref->tb.getitem(r);
ap->release (tbapi[r]);
}
}
}
}
}
/*
Get an API object from a module.
Return NULL if this API is not available.
Signal an error if several module provide the same API
(module_get_apis() should be used then)
*/
void *module_get_api (
const char *apiname,
int version,
const char *client) // Some message identifying the caller
{
void *ret = NULL;
void *tb[100];
int n = module_get_apis (apiname,version,client,tb);
if (n > 0){
ret = tb[0];
if (n > 1){
xconf_notice (MSG_U(N_BCASTAPI
,"Client %s request a single API provider for API %s\n"
"but there is more than one (%d).\n"
"First one is used.\n")
,client,apiname,n);
}
}
return ret;
}
/*
Release an API object
*/
void module_release_api (const char *apiname, void *api)
{
if (api != NULL){
void *tb[1];
tb[0] = api;
module_release_apis (apiname,tb,1);
}
}
/*
Return true if an inter-module API is available
*/
bool module_api_available (const char *apiname,int version, const char *client)
{
bool ret= false;
void *tb[100];
int n = module_get_apis (apiname,version,client,tb);
if (n > 0){
ret = true;
module_release_apis (apiname,tb,n);
}
return ret;
}
PUBLIC VIRTUAL MODULE_API_SERIAL::~MODULE_API_SERIAL()
{
}
/*
Normally, when an API_SERIAL exist, the corresponding API also exist.
There is one case where it may not be true: The serialisation has
been implemented by a module different from the one implementing the
API. The second module may be missing.
*/
PUBLIC VIRTUAL bool MODULE_API_SERIAL::is_api_ok()
{
return true;
}
class MODULE_REG_SERIAL: public ARRAY_OBJ{
public:
SSTRING apiname;
int version;
MODULE_API_SERIAL *(*get)();
/*~PROTOBEG~ MODULE_REG_SERIAL */
public:
MODULE_REG_SERIAL (const char *_apiname,
int _version,
MODULE_API_SERIAL * (*_fctget)());
/*~PROTOEND~ MODULE_REG_SERIAL */
};
PUBLIC MODULE_REG_SERIAL::MODULE_REG_SERIAL(
const char *_apiname,
int _version,
MODULE_API_SERIAL *(*_fctget)())
{
get = _fctget;
apiname.setfrom (_apiname);
version = _version;
}
static ARRAY_OBJS serials;
void module_register_api_serial (
const char *apiname,
int version,
MODULE_API_SERIAL * (*fctget)())
{
serials.add (new MODULE_REG_SERIAL(apiname,version,fctget));
}
/*
Get an API object from a module with serialisation (usable over a network).
Return NULL if this API is not available.
*/
MODULE_API_SERIAL *module_get_api_serial (
const char *apiname,
int version,
const char *client) // Some message identifying the caller
{
MODULE_API_SERIAL *ret = NULL;
for (int i=0; iapiname.cmp(apiname)==0){
if (s->version == version){
ret = s->get();
}else{
}
}
}
return ret;
}
/*
Prints all the APIs registered
*/
void module_listapi ()
{
for (int i=0; iname,r->version);
}
for (int i=0; iapiname.get(),s->version);
}
}