The DynDN.eS Blog

About DynDN.eS, eQmail, Gentoo & some other network stuff

User Tools

Site Tools


qlibs: errmsg

errmsg is a replacement for the error/strerr lib used in (net)qmail and other djb-like software. The intention to write it was a typical UNIX rules case: I was not satisfied with the existing error handling code, mainly because:

  • the code of the replaced libs was hard to read (understand) (you can feel it was written by a mathematician)
  • the strerr lib is overloaded with long static  #define 's
  • there were a lot of inconsistencies through packages and its libs, including more or less duplicate code (I hate duplicate code)
  • the error codes/messages should be come from the operating system (which is more accurat)

There are some minor reasons additional. It was clearly deliberated to make the errmsg static and not use variable args directly, but the argument *msg could be build with a variable number of arguments through the  B  macro easily (see examples below). errmsg is part of qlibs.

The switch can be done smoothly - errmsg and strerr can coexists!

Beside the man page (errmsg.3) some more explanation:

Prototype

int errmsg(char *who, int code, unsigned int severity, char *msg)
/* build error message from multiple partial strings */
extern char *build_error_msg(char *[]);
#define B(...) build_error_msg((char *[]){__VA_ARGS__,NULL})

 who  is the name of the program which runs into an error,  code  is the error code (usually  errno ),  severity  is the loglevel and  msg  is an additional internal description of the error.

There are some macros defined in  errmsg.h  which makes the usage easier:

macro equivalent (djb) description severity
 errsys(c)  hard error (code 111) system error FATAL
 errtmp(c)  temp error (code 100) temporary error (soft/user error) ERROR
 errint(c,s)  w/ additional internal defined error message          ERROR
 errlog(c,l,s)  - define code, severity and internal error message (user defined)
 log(s)  status log message log message, suppresses system error messages NOTICE

 c  is the error code,  s  is an additional message string,  l  is the severity. The arguments of errmsg which are not have to be given here (e.g.  who ) will be set automatically.

Severity and loglevel

The header file  errmsg.h  has constants defined for severity and loglevel:

/* severity constants similar to standard (see syslog.3) */
#define FATAL     0     /* ERMERGENCY (FATAL) */
#define ALERT     1     /* ALERT */
#define CRIT      2     /* CRITICAL (CRIT)*/
#define ERROR     3     /* ERROR */
#define WARN      4     /* WARNING */
#define NOTICE    5     /* NOTICE */
#define INFO      6     /* INFO */
#define DEBUG     7     /* DEBUG */
 
#define DEF_LOGLEVEL NOTICE
extern unsigned int loglevel;

The  loglevel  variable defines the behavior of errmsg in general. If

  •  loglevel < WARN : errmsg exits with (an*) error code
  •  loglevel = WARN : errmsg returns with the given error code
  •  loglevel > WARN : errmsg returns with 0

A  loglevel  greater than  DEF_LOGLEVEL  have to be set in the caller program explicitly. Usually this is done by an option like -v to increase verbosity:

int verbosity = 1;
  ...
  switch(opt) {
    case 'v': verbosity = 2; break;
    case 'q': verbosity = 0; break;
    ...
  }
  ...
  if (verbosity == 2) loglevel = INFO;

Special control of output

Some “special” error codes have an impact of the behavior of errmsg too. By default errmsg produces the following output (four parts delimited by a colon):

program: severity: system error string (code): <individual msg>

Maybe this is not wanted in some special situations. And a code of 0 (zero) doesn't indicate an error. So if  code  is not set (same as zero) and  severity  is smaller than  ERROR , then the second part ( severity: ) and the third part ( system error string (code): ) of the output will be suppressed. Example:

errlog(0,CRIT,B("usage: ",WHO," <opts> argument"));

Compatibility

For backwards compatibility there are definitions in  errmsg.h  also:

#define error_str(i) errstr(i)
 
/* djb uses some internal codes */
#define ESOFT -100      /* soft error (reversed negative) */
#define EHARD -111      /* hard error (reversed negative) */
 
/* djb backwards compat - some programs rely on an exit code 100/111 */
#define errsoft(s) errmsg(WHO,ESOFT,CRIT,s)  /* re-think severity here! */
#define errhard(s) errmsg(WHO,EHARD,ALERT,s)

Simply use the constants  ESOFT  and  EHARD  to replace the hard-coded exit codes 100/111. But in general it is recommended to use  errno  instead if possible.

Attention: Keep in mind that some programs (from djb) relies on an exit code of 100/111 (this is a rarely case)!

Thus, if  code  has the value  EHARD  or  ESOFT , errmsg exits 111 or 100 respective. See man page for details.

Further errmsg contains all the individual error definitions from djb. There is a symlink  errmsg.h –> error.h  too.

Abbreviations

The macros provide a way to make usage easier.

Example(s):

More rarely - in older code - djb uses  error_temp(errno)  like:

if (error_temp(errno)) _exit 111;

This could be replaced like:

if (errno) errsys(EHARD);

More standard usage:

#include "errmsg.h"
...
#define WHO "my progname"
 
  if (argc < 1) errsys(EINVAL);
  if (argc < 1) errmsg(WHO,EINVAL,ERROR,"");   /* call direct w/ all params */
 
  chdir("/does/not/exist");
  if (errno) errint(errno,"directory '/does/not/exist' doesn't exist");    /* print an additional message string */
  if (errno) errsys(ENOTDIR);    /* ... or short (alternative) */
 
  if (!stralloc_copys(&s,"a string")) errsys(errno);   /* short, use system message string */
 
  s = socket_tcp6();
  if (s < 0) errsys(errno);            /* print error message on failure ... */ 
  log(B("connected :",fmtnum(s)));     /* ... or log a message (depends on loglevel) */ 

Replace strerr() by errmsg()

Simply the number in a strerr_* command defines the number of arguments which will be used to create a message. So  strerr_die4x  expects 4 arguments after the error code. Some examples should make it more clear.

Hint: The constant  FATAL  in the strerr*-lines below is a hard-coded constants in the original code. They have nothing to do with the equal named severity of errmsg().

First the constant  FATAL  has to be replaced by the new constant  WHO . The severity is now defined by errmsg().

#define FATAL "bouncesaying: fatal: "  // --> remove this!
#define WHO "bouncesaying"

The new constant  WHO  have to contain the name of the program only!

The first examples are quite simple:

//  strerr_die1x(100,"Sorry, no mailbox here by that name. (#5.1.1)");
    errint(ESOFT,"Sorry, no mailbox here by that name. (#5.1.1)");
//  strerr_die2x(100,FATAL,"SENDER not set");
    errint(ESOFT,"SENDER not set");
//  strerr_die2x(111,FATAL,"out of memory");
    errint(EHARD,"out of memory");

Note: The constant  FATAL  is now replaced by the constant  WHO , which by itself will be used by the errmsg() macros automatically.

If there should be a function like  strerr_die4sys()  replaced, the long way along the examples above would be (taken from qmail-tcpok.c):

//  strerr_die4sys(111,FATAL,"unable to chdir to ",auto_qmail,": ");
    if(!stralloc_copyb(&sa,"unable to chdir to ",19)) errmem;
    if(!stralloc_cats(&sa,auto_qmail)) errsys(errno);
    if(!stralloc_catb(&sa,": ",2)) errsys(errno);
    if(!stralloc_0(&sa)) errsys(errno);
    errint(EHARD,(char *)sa.s);

This is really more effort as with the original strerr() function. But it is easy to use the  B  macro like in the next example (taken from preline.c):

//  strerr_die4sys(error_temp(errno) ? 111 : 100,FATAL,"unable to run ",*argv,": ");
    errint((errno),B("unable to run ",*argv,": "));

The part  ? 111 : 100  doesn't make sense here anymore with errmsg. As mentioned above it is recommended to use the error codes from the operating system.

Some more real examples from  qtcpclient.c :

//  strerr_warn5(CONNECT,ipstr," port ",strnum,": ",&strerr_sys);
    return(errlog(errno,WARN,B(CONNECT,ipstr," port ",strnum,": ")));
 
    strerr_warn4("tcpclient: connected to ",ipstr," port ",strnum,0);
    log(B(": AAA connected to ",ipstr," port ",strnum,0));
 
//  strerr_die4sys(111,FATAL,"temporarily unable to figure out IP address for ",hostname,": ");
    errint(errno,B("temporarily unable to figure out IP address for ",hostname,": "));
 
  if (addresses.len < 16)
//  strerr_die3x(111,FATAL,"no IP address for ",hostname);
    errint(errno,B("no IP address for ",hostname));

Makefile changes

To (re-)build an existing program against errmsg there are some changes in the Makefile necessary. Simply replace  strerr.a ,  error.a  or  error.o  /  error_str.o  /  error_temp.o  (whatever exists) in a recipe with  errmsg.a . A better way is to use a more common (standard) way with library search path(es) and library linking:

preline:
    $(COMPILE) preline.c
    $(LOADBIN) preline -Lqlibs -lqlibs

Comments