$LastChangedDate: 2007-04-27 13:08:13 +0200 (Fr, 27 Apr 2007) $
Copyright © 2005 SUSE LINUX Products GmbH
Abstract
This document describes the concept of a flexible system management library for Linux. Everything which is described here, can change on the way to it's implementation.
Unpublished Work of SUSE LINUX AG. All Rights Reserved.
This work is an unpublished work and contains confidential, proprietary, and trade secret information of SUSE LINUX AG. Access to this work is restricted to SUSE employees who have a need to know to perform tasks within the scope of their assignments. No part of this work may be practiced, performed, copied, distributed, revised, modified, translated, abridged, condensed, expanded, collected, or adapted without the prior written consent of SUSE LINUX AG. Any use or exploitation of this work without authorization could subject the perpetrator to criminal and civil liability.
General Disclaimer
This document is not to be construed as a promise by any participating company to develop, deliver, or market a feature or a product. SUSE LINUX AG makes no representations or warranties with respect to the contents of this document, and specifically disclaims any express or implied warranties of merchantability or fitness or any particular purpose. Further, SUSE LINUX AG reserves the right to revise this document and to make changes to its content, at any time, without obligation to notify any person or entity of such revisions or changes.
Table of Contents
Managing an operating system requires deep knowledge of specific system details. Even such a seemingly easy task like restarting a system service via a so called init script requires at least the knowledge of the specific LSB conform return codes such a script must return. Also configuration file locations within the file system can change as to some extend can semantics. And that's one goal of LiMaL:
Provide a system library style, objectoriented way of access to the operating system.
When the underlying system or configuration files changes, the library is going to stay the same. The current and first consumer of LiMaL is YaST. Potential future consumers are going to be some CIM providers for OpenWBEM to manage the SUSE LINUX operating system.
LiMaL is also not meant to be restricted to run on SUSE LINUX. Future versions might support other LINUX versions or even other operating systems.
LiMaL is a library written in C++, which provides a documented and unique programming interface to the operation system. It is designed to be able to add interfaces to other programming languages later on. Each feature, that LiMaL provides, must be implemented by a so called pluglib (see Chapter 3, How to write a LiMaL pluglib). Pluglibs developers have to follow some defined rules like e.g. how to handle errors or how to provide system logging and LiMaL provides some basic functionality for pluglib developers to help archiving that. LiMaL is designed that way, that both, pluglib developer and system management developer will have a documented and unique way to use LiMaL.
LiMaL uses BloCxx (see Chapter 5, What is BloCxx ?) as a generic C++ framework to implement the services it provides. BloCxx provides an system independent interface for file access, locking, logging, etc.
If there are another low-level requests, LiMaL has to fulfill these requirements.
A pluglib provides low-level access to the system.
A very detailed interface with the possibility to change every single value is required (This is a CIM requirement).
A little bit higher level API which provide a more abstract view to the configured service should also be implemented.
Parameter checks. If it is possible for an application to call a function with wrong paramters, these parameters have to be checked. (E.g. the application should provide an email address, an IP address checks if the function parameter is a string)
Script language bindings (using SWIG) to at least perl.
In order to develop and handle source code of LiMaL in a very simple and most uniform way, we provide templates of a package. This chapter explains the contents of a LiMaL package and how to handle it.
Besides the general development tools e.g. compilers a LiMaL package needs following packages installed:
BloCxx packages currently located in http://forge.novell.com/modules/xfmod/project/?limal
limal-devtools, limal, limal-devel
These packages are currently located in http://forge.novell.com/modules/xfmod/project/?limal
automake, autoconf, perl, perl-XML-Writer, libxml2, libxslt, docbook-xsl-stylesheets, docbook_4, libgcrypt, libgpg-error sgml-skel, dejagnu, expect, tcl, doxygen , openslp, openslp-devel, libpng, gpp, libgpp, libgcrypt, libgpg-error, pkgconfig, gettext, gettext-devel, swig. These packages are needed for the YaST developping environment too.
The general directory layout is
Table 3.1. directories of a package
directory | description |
---|---|
package/ | toplevel dir |
package/doc/ | documentation (manually edited) |
package/doc/autodocs/ | documentation (doxygen generated) |
package/src/ | sources |
package/src/limal/<package-name>/ | include files |
package/package | package files for autobuild |
package/testsuite | testsuite |
package/swig | Description of the SWIG interface. |
package/po | Translation files. |
Table 3.2. file description of package/
file | description |
---|---|
configure.in.in | Configuration file for autoconf/automake |
package.spec.in | Definition of the RPM .spec file. |
MAINTAINER | Person who is responsible for this package. |
POTFILES | Files which inlcude text for translation. |
Makefile.cvs | Configuration file for automake/autoconf |
RPMNAME | RPM package name |
SUBDIRS | Subdirectories of the package, the make has to go through. |
VERSION | Version in a X.Y.Z format. |
Each GENERAL package documentation will be written in DocBook-XML format. The layout stylesheet of LiMaL will be used in order to support a equal layout. The generated html files can be found in the html directory.
Table 3.3. file description of package/doc/
file | description |
---|---|
Makefile.am | Configuration file automake. For more information have a look to the template package. |
In order to document internal functions, variables,... of C/C++/Perl code we are using doxygen. This means that the documentation has to be written within the header files of source code. The format of this "inline" documentation will be explained shortly in a special chapter. While compiling the source code the concerning ducumentation will be generated automatically by doxygen and will be stored in package/doc/autodocs.
Table 3.4. file description of package/doc/autodocs
file | description |
---|---|
Makefile.am | Configuration file automake. For more information have a look to the template package. |
This is the directory in which the package files for autobuild will be generated with the call "make package" or "make package-local".
Table 3.5. file description of package/package/
file | description |
---|---|
package.changes | The documentation file for logging all package changes for the autobuild. |
The actual directory for your source code.
Table 3.6. file description of package/src/
file | description |
---|---|
Makefile.am | Configuration file automake. For more information have a look to the template package. |
The directory for all include files of a package. Only limal core packages (liblimal) install their include files directly into the src/limal/ directory.
Table 3.7. file description of package/src/limal/<package-name>/
file | description |
---|---|
Makefile.am | Configuration file automake. For more information have a look to the template package. |
The directory for the package testsuite. We are using dejagnu for testing packages. There is an own chapter how tests has to generated and handled within a LiMaL package
The directory for the translation files. During "make" a pot-file with the name <package>.pot is created. If you get back translated .po files you can check-in them into this directory with the name <lang>.po.
The LINGUAS file includes all languages to build in one line seperated by space.
With the Makevars file you can control the behaviour of gettext.
POTFILES.in has a list of all files where gettext should search for strings to translate.
If you want to create a new LiMaL package you can generate a template with the following commands:
Check out the LiMaL SVN head with the command:
svn co https://svn.suse.de/svn/limal/limal-head. The svn password is the same as the YaST svn login.
Go to directory "source":
cd source.
Generate a new package with the call:
/usr/share/limal/devtools/bin/create-new-package <skeleton> <new-package-name> <maintainer> <email>
/usr/share/limal/devtools/bin/create-new-package -s shows a list of available skeletons.
You have to put your source code to <new-package-package>/src and your include files into <new-package-name>/src/limal/<new-package-name>/.
If you need a define in a C++ file you have to pass it to the compiler. Simply use AM_CXXFLAGS = -DEXAMPLE=\"example\" in your Makefile.am.
Create the configure files, makefiles and package files with the command described in the next chapter.
In oder to create configure files, makefiles and package files there are some predinfed make targets available which are very useful and unify the developping process of a LiMaL module.
Generates all configure files and Makefiles by using autoconf and automake.
Install your compiled code into the predefined directories. You can set the environment variable DESTDIR in order to change the destination.
Plaese take care that you have to call the command with root permissions.
Create a tarball from your module and put it into the package/ directory. This also creates a spec file from the .spec.in file.
This checks for cvs consistency (see below) and whether or not you correctly tagged that version (don't forget to increase the version number in VERSION and call limaltool tagversion!), then does everything "make package-local" did.
This makes a package (i.e. it does everything "make package" does and checks it into the correct SuSE Linux distribution.
This requires /work/src/done to be mounted via NFS.
The pkg-config program is used to retrieve information about installed libraries in the system. pkg-config retrieves information about packages from special metadata files. These files are named after the package, with the extension .pc. By default, pkg-config looks in the directory /usr/lib/pkgconfig for these files. It will also look in the colon-separated list of directories specified by the PKG_CON-FIG_PATH environment variable.
The LiMaL package environment delivers a pkg-config mechanism which is activated (on default) in the configure.in.in file:
## lib configure.in.in ## initialize @LIMAL-INIT-COMMON@ @LIMAL-INIT-PROGRAM@ CREATE_PKGCONFIG=true LIMAL_APACHE_LIB_VERSION=${VERSION} LIMAL_APACHE_API_VERSION=${VERSION%%.*} AC_SUBST(LIMAL_APACHE_LIB_VERSION) AC_SUBST(LIMAL_APACHE_API_VERSION) micro=${VERSION##*.} minor=${VERSION#*.} minor=${minor%.*} cur=${LIMAL_APACHE_API_VERSION} rev=$(( $minor * 100 + $micro )) LIB_VERSION_INFO=${cur}:${rev} AC_SUBST(LIB_VERSION_INFO) ## some common checks @LIMAL-CHECKS-COMMON@ @LIMAL-CHECKS-PROGRAM@ @LIMAL-CHECKS-LIMAL@ @LIMAL-CHECKS-SWIG@ @LIMAL-CHECKS-PERL@ #AC_ARG_ENABLE(debug,[ --enable-debug debug msg for Rep ],[DEBUG_FLAGS="-DAPACHE_DEBUG"]) #AC_SUBST(DEBUG_FLAGS) ## and generate the output @LIMAL-OUTPUT@
If there is no <packagename>.pc.in file defined in the package root directory, the make command will generate this file automatically with following entries:
prefix=@prefix@ exec_prefix=@exec_prefix@ bindir=@bindir@ libdir=@libdir@ includedir=@includedir@ limalbindir=@limalbindir@ limallibdir=@limallibdir@ limaldatadir=@limaldatadir@ limalincluledir=@limalincludedir@ Name: @RPMNAME@ Version: @VERSION@ Description: LiMaL package "@RPMNAME@" Requires: limal Cflags: -I${includedir} Libs: -L${limallibdir} -l@RPMNAME@
SWIG reads annotated C/C++ header files and creates wrapper code (glue code) in order to make the corresponding C/C++ libraries available to other languages, or to extend C/C++ programs with a scripting language.
In order to use SWIG in your package you have to check the SWIG environment by
calling the check rule @LIMAL-CHECKS-SWIG@ defined in configure.in.in
:
. .. ... @LIMAL-CHECKS-SWIG@ ... .. .
This function checks the useability of SWIG and returns the library path of SWIG (e.g. /usr/lib/swig1.3). You can use the variable @SWIG_LIB@ in your Makefile.am files in order to get the path of the SWIG library.
LiMaL is using the well known gettext mechanism for translating messages like errors, warnings,....
The concerning translation files ( *.mo files ) will be stored in /usr/share/locale.
With the make call in the package root directory it extracts all translation strings from the source code. The result will be a po/*.pot file with which you can handle your translation. Translated pot files get the name <lang>.po and are checked-in into the po/ directory. By enhancing the LINGUAS file the po-files are compiled to mo-files.
In order to handle language translations in your source code you have to take care about few points which depend on the selected programming language:
If you are using the LiMaL package template almost everyting will be defined for handling translations. Please have a look into the package template for internal information. Otherwise you have to take care about only few things:
Strings which have to be translated are marked with __("<string>") in the C/C++ source code.
This section describes how the external interface of a LiMaL pluglib should look like. It defines the types which are allowed, the namespaces we have to use, the error handling and a naming guide for classes and methods.
If there is a specific need to support additional types, please keep in mind, that for every additional type a SWIG interface must be provided.
LiMaL (as well as BloCxx) is
using a version in its namespace, that is defined in the
limal/config.h
header file:
namespace LIMAL_NAMESPACE {} namespace limal = LIMAL_NAMESPACE;
The LIMAL_NAMESPACE
is a macro, that
appends the limal library version to limal
,
resulting in a namespace followed by a version number, for example
limal1
.
This special library namespace version is a simple integer, that is
incremented when the interface is changed.
Since limal
is a alias to
LIMAL_NAMESPACE
, you can simply use
the limal
shortcut, except in namespace
declarations (see bellow):
#include <blocxx/String.hpp> #include <limal/Logger.hpp> namespace // anonymous { void doSomething(const blocxx::String &str) { limal::Logger logger("TheNameOfCurrentComponent"); LIMAL_SLOG(logger, "DEBUG", "doing something with '" << str << "'"); // ... } } // End of anonymous namespace
In namespace declarations, the
LIMAL_NAMESPACE
macro should be used.
For example, if you are writing a MyLib
pluglib
and want to declare a sub-namespace below of the
limal
namespace, you have to include the
limal/config.h
header file
and use the namespaces as follows:
#include <blocxx/String.hpp> #include <limal/config.h> // declare namespace with version: namespace LIMAL_NAMESPACE { #define MYLIB_NAMESPACE MyLib1 namespace MYLIB_NAMESPACE {} namespace MyLib = MYLIB_NAMESPACE; } // use namespace-macro in declarations: namespace LIMAL_NAMESPACE { namespace MYLIB_NAMESPACE { class MyClass { public: MyClass(const blocxx::String &str = ""): myStr(str) {} ~MyClass() {} void doit() { imal::Logger logger("MyLib1"); LIMAL_SLOG_DEBUG(logger, "doing something with '" << str << "'"); // ... } private: blocxx::String myStr; }; } // End of MYLIB_NAMESPACE } // End of LIMAL_NAMESPACE
Each pluglib has to use a namespace. Of course, it is strongly recommended to use namespaces with versions in all pluglibs!
To declare own namespace, the helper macro
LIMAL_DEFINE_NAMESPACE(name,version)
can be used. See also the configure.in.in
and limal/config.h
in
limal core library, how to declare a namespace with appended
version for your package.
The skeleton in limal-devtools
provides
all required functionality.
This is the list of basic types that are allowed to be used in the interface. These types are defined in BloCxx.
#include <blocxx/Types.hpp> blocxx::Int32 = signed integer 32bit blocxx::UInt32 = unsigned integer 32bit blocxx::Int64 = signed integer 64bit blocxx::UInt64 = unsigned integer 64bit bool = (normal C++ bool) or #include <blocxx/Bool.hpp> blocxx::Bool = boolean
Some other basic types are also allowed:
#include <sys/types.h> size_t = size_t (architecture dependent) #include <time.h> time_t = datetime datatype (architecture dependent)
BloCxx has a DateTime class which internaly uses time_t. You have a constructor with time_t as parameter and and a get() method which returns time_t.
It is also allowed to use
enum = enumerations struct = structures
in the external interface.
As string in the external interface we use
#include <blocxx/String.hpp> blocxx::String
More information can be found here.
As list in the external interface we use
#include <blocxx/List.hpp> blocxx::List<ValueType>
The BloCxx List is mainly a wrapper around std::list. It stores the elements in a linked list, allowing fast insertions and deletions.
More information can be found here.
Alternatively to a BloCxx List, a BloCxx Array can be used as well, if you want contiguous elements stored as an array.
#include <blocxx/Array.hpp> blocxx::Array<ValueType>
The BloCxx Array is mainly a wrapper around std::vector. Accessing its members or appending elements can be done in constant time, whereas locating a specific value or inserting elements into the vector takes linear time.
More information can be found here.
As a specialized Array, BloCxx defines a StringArray type, that is nothing else than Array<String>. It is used very often in diverse BloCxx classes.
As map in the external interface we use
#include <blocxx/Map.hpp> blocxx::Map<KeyType,ValueType>
The BloCxx Map is mainly a wrapper around std::map.
More information can be found here.
For binary data we have implemented the limal::ByteBuffer class.
#include <limal/ByteBuffer.hpp> limal::ByteBuffer b;
More information can be found here.
Script languages like YCP or Perl are using the LiMaL library via a SWIG wrapper, that automatically wraps all objects and its public methods and embeds them into objects of the destination language. This works fine also for methods using references.
class Foo { }; class Bar { Foo getFoo(); void setFoo(const Foo &foo); };
If you want to implement reference counting, you can just derive
your class from blocxx::COWReferenceBase
and typedef using the blocxx::COWReference
.
The blocxx::COWReference
classes provides
a smart pointer with non-intrusive reference counting and
support of a 'copy on write' functionality.
To provide the 'copy on write' mechanism, the base class should
provide a clone() method creating a copy of itself. For example:
class Foo: public blocxx::COWReferenceBase { Foo * clone() { return new Foo(*this); } }; typedef blocxx::COWReference<Foo> FooRef; class Bar { FooRef getFoo(); void setFoo(const FooRef &fooRef); };
There are also other Reference classes in blocxx, for example
blocxx::COWIntrusiveReference
. Choose
one of them or use blocxx::RefCount
to
write a class providing the required functionality. But make
sure, the SWIG interface works propelly.
LiMaL uses exceptions to handle errors.
Due to the fact LiMaL uses BloCxx types and BloCxx itself uses the STL very often we have three types of exceptions in the external interface - LiMaL, BloCxx and STL exceptions.
The LiMaL Exceptions are derived from the BloCxx exceptions which are derived from the STL exceptions.
The LiMaL exceptions are only a very small set of common exception types which are not part of BloCxx. A pluglib could throw LiMaL exceptions as well as BloCxx exceptions via the BLOCXX_THROW() marco. You have only to take care for the correct namespace.
Here is a list of the LiMaL exceptions
RuntimeException
OverflowException
SyntaxException
ValueException
SystemException
If a pluglib writer wants a special exception he is able to create them with some macros which BloCxx provides.
// declaration in the header file namespace MYPLUGLIB_NAMESPACE { BLOCXX_DECLARE_EXCEPTION(<NAME>); } // implementation in a .cpp file namespace MYPLUGLIB_NAMESPACE { BLOCXX_DEFINE_EXCEPTION(<NAME>); }
LiMaL provides callback support, but it is strongly recomended to avoid callbacks in the interface at all.
The main problem with callbacks are different signatures (the argument list and return value) of the callback functions, that requires a manually wrapping of each callback function signature and for each supported destination language (see Section 2.1, “What should a pluglib provide”).
LiMaL supports callbacks, providing a template class
limal::CallbackBase<Request,Result>
.
The template allows to specialize the callback using two classes
Request
and Result
,
representing the arguments and the return value of the callback
method. As result, the callback class makes use of only one
callback function (method) signature that is embedded in a callback
object. For example:
// // declare your Request (argument) and Result (return value) classes // class MyCBRequest { /* ... */ }; class MyCBResult { /* ... */ }; // // specialize the callback interface for MyCBRequest/MyCBResult // typedef limal::CallbackBase<MyCBRequest,MyCBResult> MyCallback; // // declare and implement the function calling the specialized callback // int doit_using_a_callback(MyCallback *cb);
Using this interface for callbacks enables us to provide a generic SWIG
wrapper (currently implemented for perl only). For more informations,
see the documentation of the limal::CallbackBase
and example in the limal_Callback.i
providing
macros to specialize and instantiate a wrapped callback with SWIG.
This is a naming guide for LiMaL pluglibs. It defines standardized naming in the public APIs of pluglibs.
The function name consists of a several words. Only the first one is lowercase, every other word must start with an uppercase letter, for example createClientCertificate.
Class names begin with a uppercase character.
create<What>(...) To create a new, non-existent ... entity
get<What>(...) To read, get, extract, ... an existing entity.
set<What>(...) Commit the changes done via the entity to the configuration cache.
destroy<What>(...) Frees the entity, throwing away all changes done via it if not called Set before.
remove<What>(...) To delete, remove ... an entity.
init<What>(...) To read/initialize the data in a pluglib
commit<What>(...) To write/perform the changes for pluglibs with caches.
get<What>(...) To read, get, extract, ... as specific value of the entity.
is<What>(...) To read, get, extract, ... a specific boolean value of the entity.
enable<What>(...) To enable, set as true ... a specific boolean value of the entity.
disable<What>(...) To disable, set as false ... a specific boolean value of the entity.
set<What>(...) To set, write, ... the specific value of the entity or an existing entity.
If there is good reason, other function prefixes are allowed. For example in CA-Management we have revokeCertificate or revokeCA. This is because one does not delete a certificate, it is revoked. This is a habitual language use and quite a good reason to use the Revoke prefix.
BloCxx provides very flexible configureable logging mechanisms with a log4j like interface. It provides implementations of common Loggers allowing to log to syslog, file, stderr.
For more informations about the BloCxx Logger implementation, see BloCxx documentation, logging.txt and the examples directory in the blocxx source package.
Additionally to the BloCxx Logger, LiMaL
provides a limal::Logger
(helper) class, that allows a
simplified usage of the blocxx::Logger
inside of a PlugLib.
The PlugLib has only to create a limal::Logger
instance
and pass its name to the constructor. The name will be used as a local component
name for all log messages. A PlugLib with the name "xXx" may use the logger as in
the following example:
#include <limal/Logger.hpp> #include <blocxx/Format.hpp> // create Logger instance limal::Logger logger("xXx"); // and use it via generic macro: LIMAL_SLOG(logger, "DEBUG", "doing something with '" << str << "'"); // or a more specific one: LIMAL_SLOG_DEBUG(logger, "doing something with '" << str << "'"); // alternatively using the blocxx::Format class: LIMAL_LOG(logger, "INFO", blocxx::Format("Formating %1 with %2.", "/dev/hda1", "reiserfs")); // or also using a temoporary object LIMAL_LOG(limal::Logger("yYy"), "ERROR", "What's this?!");
As you can see in above example, there are LIMAL_LOG
and LIMAL_SLOG
macros. The difference is that
LIMAL_LOG
expects an expression that evaluates into
a String
object.
The LIMAL_SLOG
variant allows to use the stream
output operator << to construct the message.
Each pluglib generated from limal-XXxx
skeleton,
provides also a pluglib private
Utils.hpp
header file, that contains a once more simplifing macro
LOGIT(level,message)
,
initializing the limal::Logger
instance with
the name of the pluglib as local component.
The Application using BloCxx or LiMaL
libraries has to register either a default or a per thread Logger instance
to BloCxx using the
Logger::setDefaultLogger()
or
Logger::setThreadLogger()
functions provided in
blocxx
and limal
namespaces.
If the Application forgets to register a logger, the messages from
LiMaL and BloCxx libraries are
discarded (sent to the NullLogger, that will be created on demand).
#include <limal/Logger.hpp> // component and category filters blocxx::Array<blocxx::String> components(1, "*"); blocxx::Array<blocxx::String> categories(1, "*"); // create a Logger instance blocxx::LoggerRef logger( limal::Logger::createSyslogLogger( // default component name, filters "MyApp", components, categories, // log message format definition "%r [%d] %p %c - %m", // syslog identity and facility "myapp", "user" ); // register it as default logger limal::Logger::setDefaultLogger(logger); // or for each thread: limal::Logger::setThreadLogger(logger);
Alternatively the Application can use the BloCxx Appender interface directly, for example:
#include <blocxx/Logger.hpp> #include <blocxx/AppenderLogger.hpp> #include <blocxx/FileAppender.hpp> // create a LogAppenderRef to a FileAppender blocxx::LogAppenderRef appender(new FileAppender( blocxx::LogAppender::ALL_COMPONENTS, blocxx::LogAppender::ALL_CATEGORIES, "/path/to/log.file", FileAppender::STR_DEFAULT_MESSAGE_PATTERN, 0, // maximal file size in kb 0 // number of backup files )); // create a LoggerRef using the appender blocxx::LoggerRef logger(new AppenderLogger( "MyApp", E_ALL_LEVEL, appender )); // register it as default logger blocxx::Logger::setDefaultLogger(logger); // or for each thread: blocxx::Logger::setThreadLogger(logger);
Or also implement own Logger or LogAppender and let blocxx use it.
See also limal/src/Logger.cpp
in the limal
repository - the above example code is stolen from the
limal::Logger::createFileLogger()
function.
BloCxx provides a Mutex class implementing a recursive mutex and a MutexLock helper class that can be used to acquire the Mutex in a scope, for example:
#include <blocxx/Mutex.hpp> #include <blocxx/MutexLock.hpp> namespace { blocxx::Mutex mutex; doitOne() { blocxx::MutexLock lock(mutex); // Just do it, we are in locked state in the // current thread as long as lock variable // doesn't leave its scope. // It is also fine to call doitTwo, since the // mutex is recursive: doitTwo(); // go on.. } doitTwo() { blocxx::MutexLock lock(mutex); // do something, we are in locked state now } }
Of course BloCxx provides also a NonRecursiveMutex class with its a NonRecursiveMutexLock helper. Synchronization using a conditional variable is provided by the Condition, ConditionResource and ConditionLock classes.
BloCxx provides an Interprocess Locking Mechanism which is described here.
BloCxx has an own file handling class which includes a fctnl based locking mechanism:
trylock
Acquire a kernel lock on the file. It returns immediately if the file has been already locked.
getLock
Acquire a kernel lock on the file. If the file has been already locked the function will wait until the file will be unlocked by the other process.
unlock
Release a lock on the file.
In order to manage system commands on the local machine, do NOT use calls like the "system" command of C. For security reasons please use the BloCxx calls which are provided in the Exec namespace.
The limal package provides classes for getting file information and manipulate files ( location, attributes,...):
Wrapper class for ::stat/::lstat and other file/directory related operations
Handling file or pathname of a file.
This class provides a set of functionality for reading and writing INI files. The parser can be configured for almost each entry/value/comment format by using regular expressions. For more information have a look into the online docu of the limal package. Meanwhile there is an example for reading/writing a sysconfig file:
#include <limal/INIParser.hpp> using namespace blocxx; using namespace limal; using namespace limal::INI; blocxx::Array<Options> options; // Options like NO_NESTED_SECTIONS, LINE_CAN_CONTINUE, ... blocxx::StringArray commentsDescr; // Regular expression of the comments description blocxx::Array<SectionDescr> sectionDescr; // Regular expression of a section description blocxx::Array<EntryDescr> entryDescr; // Regular expressions for entries (keys/values). blocxx::Array<IoPatternDescr> rewrites; // rules for writing key/value options.append (GLOBAL_VALUES); // Values at the top level(not in section) are allowed options.append (LINE_CAN_CONTINUE); // if there is \ at the end of line, options.append (COMMENTS_LAST); // Lines are parsed for comments after they are parsed for values // comment description commentsDescr.append("^[ \t]*#.*$"); commentsDescr.append("#.*"); commentsDescr.append("^[ \t]*$"); // Entry (key/value) description IoPatternDescr pattern = { "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\"]*)\"", "%s=\"%s\""}; EntryDescr eDescr = {pattern, "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\"]*)", "([^\"]*)\"" , true}; entryDescr.append (eDescr); IoPatternDescr pattern2 = {"([a-zA-Z0-9_]+)[ \t]*=[ \t]*([^\"]*[^ \t\"]|)[ \t]*$", "%s=\"%s\""}; EntryDescr eDescr2 = {pattern2, "", "" , false}; entryDescr.append (eDescr2); IoPatternDescr pattern3 = {"^[ \t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \t]*=[ \t]*'([^']*)'", "%s='%s'" }; EntryDescr eDescr3 = {pattern3, "([a-zA-Z_][a-zA-Z0-9_]*)[ \t]*=[ \t]*'([^']*)", "([^']*)'" , true}; entryDescr.append (eDescr3); INIParser descParser; // which file has to be parsed ? descParser.initFiles ("/etc/sysconfig/mail"); // init the rest of parser if (!INIParser::initMachine (options, commentsDescr, sectionDescr, entryDescr, rewrites)) { return "ERROR"; } // parsing file if (!descParser.parse ()) { return "ERROR"; } return descParser.iniFile.getValue("SMTPD_LISTEN_REMOTE");
The example has been taken from the additional class "SysConfig" which is derived from INIParser:
SysConfig : public INIParser { public: SysConfig() : INIParser() {}; bool initMachine (); };
So the part of initialisation of sysconfig files is much more comfortable.
The BloCxx string is a UTF8 String implementation that includes utility functions like toUpperCase, toLowerCase, compareToIgnoreCase and trimming of strings. Where required, it automaticaly converts the string into a Wide-Character to perform operations on single characters using its own converters in the UTF8Utils namespace.
BloCxx provides an optional IConv_t class wrapping the the POSIX iconv converter supporting conversion between nearly all character encondings (at least on Linux). Further it provides some utility functions in the IConv namespace.
BloCxx provides simple PosixRegEx and PerlRegEx classes, that wrapps the POSIX/Perl regular expression interface (provided by the glibc and the pcre libraries). The classes provides also most common utilities methods like capture, replace, grep and split.
BloCxx provides the classes Socket and ServerSocket for network communication.
In order to ensure a unified look in our documentation we will use the templates of the package limal-devtools. Please ensure that this package will be installed on your developing system.
There are two kinds of documentation:
General Documentation
This documentation can be found in <package>/doc and is written in DocBook-XML.
Inline Documentation
Modules, functions and variables are described in the source code. We will use doxygen and special stylesheets. The documentation will be generated automatically by calling make
The documentation will be generated (in HTML format) in <package>/doc/autodocs.
As decribed above this kind of documentation is located in <package>/doc and is written in DocBook-XML.
A short description of DocBook-XML can be found here.
The source XML files and the stylesheets (given by package limal-devtools) are defined in the accordingly <package>/doc/Makefile.am:
# # Makefile.am for limal-utils/doc # SUBDIRS = autodocs htmldir = $(docdir) xml_files = $(wildcard *.xml) html_DATA = $(wildcard html/*.html) \ html/index.html \ html/limaldocs.css pdf: limal-utils.pdf html/index.html: $(xml_files) XML_CATALOG_FILES=@XML_CATALOG@\ @XSLTPROC@ @XSLTPROC_FLAGS@ --xinclude \ @STYLESHEET_HTML@ limal-utils.xml limal-utils.fo: $(xml_files) XML_CATALOG_FILES=@XML_CATALOG@ \ @XSLTPROC@ @XSLTPROC_FLAGS@ --xinclude \ -o $@ @STYLESHEET_PDF@ limal-utils.xml limal-utils.pdf: limal-utils.fo fop -q $< $@ html/limaldocs.css: html/index.html cp @STYLESHEET_CSS@ html cp -a $(limaldatadir)/docbook/images html EXTRA_DIST = $(xml_files) CLEANFILES = $(html_DATA) clean-local: rm -rf html install-data-local: mkdir -p $(DESTDIR)$(htmldir) cp -a $(srcdir)/html/images $(DESTDIR)$(htmldir)
This is an example for a source XML file. Every XML file should have following attributes at least:
<?xml version="1.0" encoding='ISO-8859-1'?> <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "/usr/share/xml/docbook/schema/dtd/4.2/docbookx.dtd" [ <!ENTITY version "2.10.0"> <!ENTITY dx "DocBook-XML"> <!ENTITY date "Januar 2005"> ]> <book id="limal"> <bookinfo> <title>LiMaL Packages</title> <subtitle>LiMaL package description.</subtitle> <author> <firstname>Stefan</firstname> <surname>Schubert</surname> </author> <copyright><year>2005</year><holder>SUSE LINUX GmbH</holder></copyright> <abstract> <para>LiMaL Package Description. Version 0.0.1 - Januar 2005</para> </abstract> </bookinfo> <chapter id="main"> . .. ... .. . </chapter> </book>
Modules, functions and variables are described in the source code. We will use doxygen and special stylesheets. The documentation will be generated automatically by calling make
The documentation will be generated (HTML format) in <package>/doc/autodocs.
First of all you have to configure the concerning <package>/doc/autodocs/Makefile.am:
# # Makefile.am for limal-utils/doc/autodocs # htmldir = $(docdir)/autodocs html_DATA = index.html $(wildcard *.html *.png) doxygen.css @RPMNAME@.tag CLEANFILES = $(html_DATA) doxygen.log doxygen.conf installdox doxygen.css @RPMNAME@.tag: index.html header_dir = $(srcdir)/../../src/limal/utils/ index.html: $(header_dir)/*.hpp ${LIMALDOXYGEN} --prefix=$(prefix) PROJECT_NAME=@RPMNAME@ TAGFILES= INPUT=$(header_dir)
You have to declare which files ( e.G *.h ) have to be parsed by doxygen in order to generate the documentation of modules, functions, variables, ...
Lets have a look to a short example in order to show the available tags:
File: SysConfig.h Author: Cornelius Schumacher <cschum@suse.de> Maintainer: Cornelius Schumacher <cschum@suse.de> /-*/ #ifndef SysConfig_h #define SysConfig_h #include <string> #include <map> /** @brief Reading and Writing Configuration files. */ /** This class provides access to the configuration files under /etc/sysconfig. A sysconfig file consists of lines of key/value pairs. It can also contain empty lines and comments (lines starting with "#"). Keys are case-sensitive. Values can be quoted like 'value' or "value". Whitesspace at the beginning or end of keys and values (outside of quotes) is removed. */ class SysConfig { public: /** Construct a sysconfig object. Reads the given file. @path Path to sysconfig file. This can either be an absolute path or a path relative to /etc/sysconfig. */ SysConfig( const char *path ); ~SysConfig(); /** @return string entry @param key Key of entry. @param defaultValue Default return value, if key doesn't exit. */ std::string readEntry( const std::string &key, const std::string &defaultValue = std::string() ); }; #endif
After creating the Makefile with make -f Makefile.cvs in the package root directory, the document will be generated automatically by every calling of make.
The documentation will be generated (HTML format) in <package>/doc/autodocs.
For the example (described above) the output file classSysConfig.html would be generated.
Here is a list of common used parameters in doxygen:
brief
Starts a paragraph that serves as a brief description. For classes and files the brief description will be used in lists and at the start of the documentation page. For class and file members, the brief description will be placed at the declaration of the member and prepended to the detailed description. A brief description may span several lines (although it is advised to keep it brief!). A brief description ends when a blank line or another sectioning command is encountered. If multiple @brief commands are present they will be joined.
return
Starts a return value description for a function. The text of the paragraph has no special internal structure. All visual enhancement commands may be used inside the paragraph. Multiple adjacent @return commands will be joined into a single paragraph. The \return description ends when a blank line or some other sectioning command is encountered.
param <parameter-name>
Starts a parameter description for a function parameter with name <parameter-name>. Followed by a description of the parameter. The existence of the parameter is checked and a warning is given if the documentation of this (or any other) parameter is missing or not present in the function declaration or definition.
The @param command has an optional attribute specifying the direction of the attribute. Possible values are "in" and "out". Here is an example for the function memcpy:
/*! Copies bytes from a source memory area to a destination memory area, where both areas may not overlap. @param[out] dest The memory area to copy to. @param[in] src The memory area to copy from. @param[in] n The number of bytes to copy */ void memcpy(void *dest, const void *src, size_t n);
If a parameter is both input and output, use [in,out] as an attribute.
The parameter description is a paragraph with no special internal structure. All visual enhancement commands may be used inside the paragraph.
Multiple adjacent @param commands will be joined into a single paragraph. Each parameter description will start on a new line. The @param description ends when a blank line or some other sectioning command is encountered.
exception <exception-object>
Starts an exception description for an exception object with name <exception-object>. Followed by a description of the exception. The existence of the exception object is not checked. The text of the paragraph has no special internal structure. All visual enhancement commands may be used inside the paragraph. Multiple adjacent @exception commands will be joined into a single paragraph. Each parameter description will start on a new line. The @exception description ends when a blank line or some other sectioning command is encountered.
Example:
/** @brief Reading Entries @return string entry @param key Key of entry. @param defaultValue Default return value, if key doesn't exit. @exception std::out_of_range parameter is out of range. */ std::string readEntry( const std::string &key, const std::string &defaultValue = std::string() ) throw(std::out_of_range) {};
code
Starts a block of code. A code block is treated differently from ordinary text. It is interpreted as C/C++ code. The names of the classes and members that are documented are automatically replaced by links to the documentation.
endcode
Ends a block of code.
todo
Starts a paragraph where a TODO item is described. The description will also add an item to a separate TODO list. The two instances of the description will be cross-referenced. Each item in the TODO list will be preceded by a header that indicates the origin of the item.
warning
Starts a paragraph where one or more warning messages may be entered. The paragraph will be indented. The text of the paragraph has no special internal structure. All visual enhancement commands may be used inside the paragraph. Multiple adjacent @warning commands will be joined into a single paragraph. Each warning description will start on a new line. Alternatively, one @warning command may mention several warnings. The @warning command ends when a blank line or some other sectioning command is encountered.
note
Starts a paragraph where a note can be entered. The paragraph will be indented. The text of the paragraph has no special internal structure. All visual enhancement commands may be used inside the paragraph. Multiple adjacent @note commands will be joined into a single paragraph. Each note description will start on a new line. Alternatively, one @note command may mention several notes. The @note command ends when a blank line or some other sectioning command is encountered.
see or sa
Starts a paragraph where one or more cross-references to classes, functions, methods, variables, files or URL may be specified. Two names joined by either :: or # are understood as referring to a class and one of its members. One of several overloaded methods or constructors may be selected by including a parenthesized list of argument types after the method name.
b <word>
Displays the argument <word> using a bold font. Equivalent to <b>word</b>. To put multiple words in bold use <b>multiple words</b>.
This is a description of how to test your LiMaL source code.
Main task of testing your source code is to ensure same functionality after any change you do. There is usually some code depending on your source, so if you do a change, you must be sure the outer behavior remains a same.
The testsuite can be also used for testing functionality on other machines and architectures you don't have access to or on any strange hardware configurations.
And at last, with a well written testsuite you can test your code in a random circumstances, with random input and for long time like nobody alive can ever do.
The testsuite is based on dejagnu. So following packages have to be installed:
dejagnu.rpm
expect.rpm
tk.rpm
tcl.rpm
Test will be run with NO root rights. So you have to keep this in mind if you want to access resources with root permissions only.
In order to solve this problem it is useful to define functions in your lib,plugin,... for setting an testsuite environment. e.g.: setEtcDirectory(<etc-testsuite-directory>) for changing the path of the /etc directory.
The simplest way to generate testcases is to use the testsuite envirnoment of the LiMaL package template.
There are two kinds of testcases defined in this testsuite which are passed through a test run:
Single tests are called one time.
Multiple tests are called several times with several parameters.
These two testcases are defined in the directory testsuite/<program>.test:
single_test.exp
multi_test.exp
If these kinds of testcases do not fullfill your requirements you can define your own kind of testcase by adding a new file in this directory:
testsuite/<program>.test/<new-name>.exp:
While a test run single tests are called one time.
A testprogram with the name testsuite/<programname>.single will be called without parameters.
This program has been created by yourself. The program returns 0 if it has been finished successfully. The output of the program is stdout and stderr.
The output (stderr,stdout) of this testprogram will be compared with output described in testsuite/single.out/<programname>.out and testsuite/single.out/<programname>.err.
The test has been successful if the testprogram has returned 0 and the output is the same as in testsuite/single.out/<programname>.out and testsuite/single.out/<programname>.err described.
Multiple tests are called several times with several parameters.
So all programs with the extention "multi" will be execute for several times.
Each testprogram testsuite/<programname>.multi has an own subdirectory testsuite/<programname> in which several testcases are defined for this program.
Each testcase has 3 files:
<testcasename>.in
<testcasename>.out
<testcasename>.err
The program "<programname>.multi" will be called with the argument "<testcasename>.in". The standard output will be compared with the files "<testcasename>.out" and "<testcasename>.err". The testcase is successful if the program returns 0 AND the outputs are identically equal to the files "<testcasename>.out" and "<testcasename>.err".
When you have written the testprograms and have compiled it with "make -f Makefile.cvs; make" you can run it with "make check".
The standard output will be written to file testsuite/single.out/out/<programname>.out ( tests which are called one time ) or testsuite/<programname>/out/<testcase>.out ( tests which are called several time ).
The error output of multiple tests will be written to file testsuite/<programname>/out/<testcase>.err. It should be an empty file.
If the output files are OK, copy them to testsuite/single.out/ ( tests which are called one time ) or testsuite/<programname>/ ( tests which are called several time ).
Now, you have a (one) testsuite ready. The output files will be used during the next check run ( with make check ) for comparison with real output to decide if the test passed.
In order to ensure a unified look in the source code we will have to come to an agreement using the same coding style. We basically suggest the LiMaL Coding Guide:
It looks like:
Yes: blocxx::String getText() const; No: blocxx::String getText() const; Yes: blocxx::String TestNamespace::getText() const { } No: blocxx::String TestNamespace::getText() const { }
![]() | Justification: |
---|---|
Because return values can consist of long terms especially if they have a namespace in front. This leads into badly readable code, and you can't see the return value at first sight. |
Otherwise you won't be able to set the beginning of the conditions to the same column.
An example is given below:
if (functA == true || functB != true || functC == false) { } ^^^^^^^^ Tab ^^^^ Space
No: blocxx::String test; blocxx::StringArray test2; Yes: blocxx::String test; blocxx::StringArray test2;
![]() | Justification: |
---|---|
Indention helps to split up the variable name from the rest of the declaration. So the name itself is clearly readable. The same indention for all declarations helps thereby, because the eyes can stay in the same column. |
LiMaL based on BloCxx.
The BloCxx guideline is similar to the old YaST guideline.
The complete text of BloCxx Coding Guide can be found here.
BloCxx is derived from the OpenWBEM project, where it has been also used as a common set of base classes. The advantage of BloCxx is, that it is a robust set of code which has been used since a long time. Additionally it is running on all kinds of operating systems, which even includes Win32.
BloCxx is hosted on http://forge.novell.com/modules/xfmod/project/?blocxx
Similar to sourceforge, forge.novell.com hosts open source projects, which is, what BloCxx already is.
There's also a mailinglist at http://forge.novell.com/modules/xfmod/maillist/?group_id=1634
Feel free to subscribe. Discussions regarding the system management library must still take place on the list limal-devel@suse.de, however.