-
Notifications
You must be signed in to change notification settings - Fork 96
Carpal
Error handling is a difficult task. It’s very easy to forget to trap an error but just one ignored error code is enough to screw up the whole application.
LibU carpal ¹ is a set of macro used for error handling, flow control and debugging purpose. It also try to enforce a coding policy that helps LibU users writing robust C/C++ programs.
In this tutorial we’ll write a program that counts the number of ‘a’ in a document. It’s not an advanced coding project but it will be useful to cover most carpal macros.
Let’s start from scratch:
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <u/libu.h>
int facility = LOG_LOCAL0;
int main(int argc, char **argv)
{
dbg_err_if(argc < 2);
printf("success\n");
return EXIT_SUCCESS;
err:
printf("failure\n");
return EXIT_FAILURE;
}
Compile it:
gcc -DDEBUG -o carpal-0 carpal-0.c -I libu/u libu/src/libu.a
dbg_err_if() evaluates the given expression and if it’s true writes debugging info to the log file (using system log facility). After that it jumps to the error secion at the bottom of the function (it actually executes a “goto err”).
The log file used depends on the configuration of the syslog daemon; for each carpal-using program you must define an integer variable called “facility” that needs to be initialized with the syslog facility number you want to use.
This is what we’ll read in the log file running the example program without any argument:
Oct 25 13:48:59 pb ./carpal-1: [dbg][12090:carpal-1.c:26:main] argc < 2
There are many information in a single line: - the timestamp of when the error occurred - the hostname - the program name: carpal-1 - the priority of the log message: [dbg] - the PID of the program: 12090 - the name of the source file and line number: carpal-1.c:26 - the funcion in which the failing code block is: main() - the failed expression: argc < 2
Let’s go a step further in the implementation of our program.
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <u/libu.h>
int facility = LOG_LOCAL0;
int openfile(const char *file, int *pfd)
{
int fd = -1;
dbg_err_if((fd = open(file, O_RDONLY)) < 0);
/* return the file descriptor */
*pfd = fd;
return 0; /* ok */
err:
return ~0; /* error */
}
int main(int argc, char **argv)
{
int fd = -1;
dbg_err_if(argc < 2);
dbg_err_if(openfile(argv[1], &fd));
/* do something here */
close(fd);
printf("success\n");
return EXIT_SUCCESS;
err:
printf("failure\n");
return EXIT_FAILURE;
}
Now launch the program with an not-existant file as argument (./carpal-1 dummyfile). This is how your log will look like (timestamps and hostname stripped off):
./carpal-1: [dbg][12110:carpal-1.c:12:openfile] (fd = open(file, 0x0000)) < 0
./carpal-1: [dbg][12110:carpal-1.c:28:main] openfile(argv[1], &fd)
The first line is printed out because the open() syscall failed; note that the log tell us exactly what failed and where it is localed in the source code. Reading on we see that also the openfile() function fails and the same happens to the main() function.
Now let’s add some more code and run the program (again with a bad filename).
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <u/libu.h>
int facility = LOG_LOCAL0;
int openfile(const char *file, int *pfd)
{
int fd = -1;
dbg_err_if((fd = open(file, O_RDONLY)) < 0);
/* return the file descriptor */
*pfd = fd;
return 0; /* ok */
err:
return ~0; /* error */
}
int process_file(const char *file)
{
int fd = -1, c, i, count = 0;
char buf[256];
dbg_err_if(openfile(file, &fd));
/* do something here */
close(fd);
return 0;
err:
return ~0;
}
int main(int argc, char **argv)
{
dbg_err_if(argc < 2);
dbg_err_if(process_file(argv[1]));
printf("success\n");
return EXIT_SUCCESS;
err:
printf("failure\n");
return EXIT_FAILURE;
}
./carpal-2: [dbg][12142:carpal-2.c:12:openfile] (fd = open(file, 0x0000)) < 0
./carpal-2: [dbg][12142:carpal-2.c:26:process_file] openfile(file, &fd)
./carpal-2: [dbg][12142:carpal-2.c:41:main] process_file(argv[1]
What should be clearly visible here is that we’re reading a backtrace. The open() syscall failed so the openfile() returned an error; the process_file() function that uses the openfile() funcion also failed and the error propageted till the main() function that exited with EXIT_FAILURE.
So with little effort we have now a program that logs the exact execution path when something goes wrong.
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <u/libu.h>
int facility = LOG_LOCAL0;
int openfile(const char *file, int *pfd)
{
int fd = -1;
dbg_err_sif((fd = open(file, O_RDONLY)) < 0);
/* return the file descriptor */
*pfd = fd;
return 0; /* ok */
err:
return ~0; /* error */
}
int process_file(const char *file)
{
int fd = -1, c;
char buf[256];
dbg_err_ifm(openfile(file, &fd),
"openfile error! input file %s", file);
/* do something here */
for(;;)
{
dbg_err_sif((c = read(fd, buf, sizeof(buf))) < 0);
if(c == 0)
break; /* eof */
/* process the buffer */
}
close(fd);
return 0;
err:
if(fd >= 0)
close(fd);
return ~0;
}
int main(int argc, char **argv)
{
dbg_err_if(argc < 2);
dbg_err_if(process_file(argv[1]));
printf("success\n");
return EXIT_SUCCESS;
err:
printf("failure\n");
return EXIT_FAILURE;
}
There are few differences here: we used dbg_err_sif() (_sif not _if) for library functions
Here we have two new macros: dbg_err_sif() and dbg_err_ifm().
The first is just like a plain dbg_err_if() but also logs strerror(errno) string (so it can be used for all function calls that set the global ‘errno’ variable). If you want to log the string returned by strerror(errno) without jumping to the ‘err:’ label use dbg_strerror(errno) instead.
Otherwide dbg_err_ifm() is just like a dbg_err_if() but logs the message given by the developer instead of the expression passed to the macro itself. The message is a printf-style format string so it can be followed by a variable length argument list.
Another important thing to notice is that we used the ‘err:’ block of process_file() to cleanup all resources we allocated in the previous lines of the funcion; so if an error occurred in the read() syscall we have the opportunity to free the resources we used before returning from the function.
This is required if you don’t want (I know you don’t :) ) leak memory, file descriptor, etc. and this is the kind of coding style that carpal macros enforce.
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <u/libu.h>
int facility = LOG_LOCAL0;
int openfile(const char *file, int *pfd)
{
int fd = -1;
dbg_err_sif((fd = open(file, O_RDONLY)) < 0);
/* return the file descriptor */
*pfd = fd;
return 0; /* ok */
err:
return ~0; /* error */
}
int process_file(const char *file)
{
int fd = -1, c, i, count = 0;
char buf[256];
dbg_err_ifm(openfile(file, &fd),
"openfile error! input file %s", file);
/* do something here */
for(;;)
{
dbg_err_sif((c = read(fd, buf, sizeof(buf))) < 0);
if(c == 0)
break; /* eof */
/* process the buffer */
/* count 'a's */
for(i = 0; i < c; ++i)
{
if(buf[i] == 'a')
{
count++;
if((count % 10) == 0)
dbg("count: %d", count);
}
}
}
dbg_if(count == 0);
con("number of 'a': %d", count);
close(fd);
return 0;
err:
if(fd >= 0)
close(fd);
return ~0;
}
int main(int argc, char **argv)
{
dbg_err_if(argc < 2);
dbg_err_if(process_file(argv[1]));
printf("success\n");
return EXIT_SUCCESS;
err:
printf("failure\n");
return EXIT_FAILURE;
}
In this step we added the code block that counts the ‘a’ letters and writes the number of ‘a’ counted so far every 10 ‘a’. We used the dbg() macro that is a printf-style function that write to the log (using [dbg] priority).
Another new macro is dbg_if() that logs the expression source code when the expression is true.
Then we used the con() macro that works just like the dbg() macro but outputs the string to the console instead of the log file. The same can be applied to other macros we know...if we change dbg_ in con_ we can use the same macros to write messages to the console. We can also keep logging data using different priorities using warn_, info_, err_ prefixes instead of dbg_.
This is the list of defined macros where (pfx_ can be on of the following prefix: con, dbg, warn, err, info, nop):
- pfx()
- pfx_err()
- pfx_if()
- pfx_ifb()
- pfx_return_if()
- pfx_return_sif()
- pfx_err_if()
- pfx_err_sif()
- pfx_err_ifm()
- pfx_goto_if()
- pfx_strerror()
You should know all macros except pfx_err() that output a message and jump to ‘err:’, pfx_return_if() that output a message and return the given value, pfx_goto_if() that go to the given label if the expression is true.
(¹) it’s called carpal because it helps coders saving precious key strokes that eventually will keep them far from carpal tunnel syndrome :)