uCommon Telephony Libraries

The idea of a new phone system server project on embedded hardware, David created a free common embedded library.

With uCommon I brought forward and updated some of the functions that were part of the original portable library found in the DBS Server. I also looked at my current Common C++ for further inspiration. I am now happy with the result and will be distributing this package as uCommon-R1 by the time this article is in print.

While most packages use a major.minor numbering scheme, including most of my own, I have chosen to use a single version release designation for uCommon. This represents the very low frequency of change that I expect with the library now that it has been tested through.

Much like Common C++, uCommon can be broken down into several fixed categories of functionality. These include configuration file parsing, user/password management and authentication, process management, sockets, serial I/O and string manipulation. These represent things that I believe are useful and necessary in many of the embedded applications I would imagine writing.

These functional families can be viewed as building upon what (g)libc or embedded runtimes like uClibc already provide. In this sense uCommon is meant to augment existing runtime services and the existing libc, rather than replace it. uCommon is also meant to compile either on a standard glibc-based Linux host or to be used in building embedded applications that use things like diet-c or uClibc.

The process model was chosen for uCommon rather than threading, as found in Common C++ for example, because most uC and similar tiny embedded Linux systems do not include a thread-base library like pthread. In fact, I am not even certain if the smaller libc derivatives commonly used on these systems are threadsafe or reentrant.

Within the process model, uCommon offers a number of IPC services and wrappers to make life easier when writing application services that are fork( )'d. These services include pidpipe, pidpacket and pidstream. pidpipe forms a two-way process pipe to a detached fork through two anonymous pipe handlers (much like what the | shell operation does). pidpacket uses UNIX domain UDP sockets for discreet message (packet) passing between a client and server process. pidstream uses an anonymous UNIX domain stream socket. pidpipe has the advantage and limitation that the size of the system kernel buffering is both known and small (4K).

Another convenience function is the pidfile. This implements the .lock file logic commonly found when protecting a filesystem resource against arbitrary access from multiple processes. The pidfile implementation provided is clean from common race conditions. While pid files do not work very well on network-mounted filesystems, this is not an issue with embedded applications.

Another set of process functions use SIG_ALRM to implement a process timer. This differs from using ``alarm'' alone in that the code needed to test whether a timer has expired exists directly, and one does not have to create a signal handler using these services.

Process functions signal_posix and signal_intr clarify and standardize signal handling. With signal_posix, true POSIX behavior is assured, while signal_intr can be used for signal handlers that potentially interrupt system calls.

One group of functions form a paged-memory allocator, which can be used to allocate small chunks of memory incrementally from pre-allocated page buffers. This will eliminate the need for many multiple ``malloc'' operations and fragmented memory blocks that can also prove very wasteful of memory in an embedded system. All allocated memory can be disposed of cleanly at once with freepager (uCommon library function that purges the allocation from the paged-memory allocator).

Finally, the process group of uCommon includes functions which make the creation and use of SysV-style semaphores easier by offering a basic interface that allows one to create, release, lock and unlock a semaphore object, without having to build semop tables in your code. A pdetach function enables a process to ``dæmonfy'' itself, essentially by detaching from the controlling terminal and making init (pid 1) its parent process.

A group of functions called keydata are used to parse configuration files. They assume such configuration files are formed from keyword pairs and grouped together under the bracketed ([ ]) section name. Hence, if one has an /etc configuration file like, for example:


one can use keydata to parse this file. The file is processed as a node of keypaths, where one complete section is loaded into memory and may then be examined. Comment lines are automatically skipped, and multiple instances of the same keyword in a given section are actually formed into a linked list.

The most common means of accessing a keydata section is with initkeydata. This will load a specified section into a keydata_t referenced object, and can include an optional list of default values. Keypaths include the file path and the section path, as referenced from /etc. Hence, one might, in the above, use:

static keyinit_t netdefs[] = {
        {"port", "7000"},
        {"bind", "*"},
        {NULL, NULL}};
keydata_t *netkeys = initkeydata("/myapp/network", netdefs);

The resulting pointer, netkeys, then holds the content of the loaded configuration file. Individual keyword values can be extracted with getkeylast. For example, to get the bind address, one might use:

char *bind = getkeylast(netkeys, "bind");
If multiple ``bind'' keywords existed in [network], the first one could be found with getkeyfirst, and getkeylist could be used to extract a list of all variants loaded. This allows for config files to contain logical lists. The getkeyindex function can be used to extract the list of keywords loaded for a given [section].

One can also manipulate the contents of the keydata_t keywords after they have been loaded into memory with setkeydata and clrkeydata. This could be used to insert override values based on argv[ ] command-line switches, for example. These values never replace the loaded ones; they are instead added to the ``chain'' of values for a given keyword and are visible with getkeylast( ). The getkeylist will return all prior values, including those set with setkeyvalue at the end.

Another set of functions covers authentication or at least, provides the minimum vrfpwent function to validate a user id and password combination against the /etc/passwd file. This function doesn't examine the /etc/shadow file because embedded Linux systems generally do not use shadow passwords or the overhead of additional passwd entry routines that this would add to the runtime libraries. The other function currently provided in the auth set, sockident, extracts the connecting user's ``local username'' using the ident server.

Serial routines are provided to handle serial I/O. While not every embedded Linux controller may have a network interface, all do seem to include serial I/O. Serial I/O is also used to integrate many external devices, as well as many kinds of telephone systems such as the Panasonic DBS.

The purpose of the uCommon serial I/O functions is to simplify serial port attributes and to provide a stable base configuration for use with binary and block I/O operations.

The first serial function is getserial, which simply opens the serial device node with the specified data format and speed, and with hardware handshaking enabled. It returns a valid file descriptor when successful.

Various I/O functions provided to read and write data from the serial device offer timeouts. Timeouts can be used to monitor a successful operation and specify when to stop waiting for more data to arrive. Some further improvements can and will be made to uCommon serial functions, perhaps even prior to distribution of R1.

The socket functions are meant to replace the use of complex sockaddr structures and name resolver libraries with simple string-based functions. Hence, one can perform a getsocket against a logical host name, for example socket = getsocket("www.linuxtoday.com", 80, SOCK_STREAM). This operation would normally involve using gethostbyname, building a sockaddr_in structure, and finally a socket and connect call. Named strings can also be used for names of interfaces to bind sockets and servers to locally.

In addition to internet connections, a similar set of convenient functions are provided for named UNIX domain sockets. Finally, sockets provide a recvline function. This performs line-oriented input from a socket descriptor directly, without the need for, or use of, stdio buffering.

Finally, uCommon provides some convenient string functions including case-insensitive comparisons, case-conversion functions and string trimming. The trim functions essentially are used to remove a specified set of lead and/or trailing characters from an existing string.