xcpc_set_exitproc, xcpc_push_tryctx, xcpc_pop_tryctx, xcpc_do_throw, xcpc_do_rethrow, xcpc_context_create, xcpc_context_reparent, xcpc_context_free, xcpc_context_parent, xcpc_context_root, xcpc_context_exhandler, xcpc_resource_add, xcpc_resource_movehead, xcpc_resource_movetail, xcpc_resource_moveafter, xcpc_resource_movebefore, xcpc_resource_ctx, xcpc_resource_data, xcpc_resource_del, xcpc_resource_reparent, xcpc_resource_set
XCPC_THROW, XCPC_RETHROW, XCPC_TRY, XCPC_CATCH, XCPC_CATCH_ANY, XCPC_END_TRY, XCPC_RETURNV, XCPC_RETURN, XCPC_EXCEPT_DATA, XCPC_EXCEPTION
xcpc__malloc, xcpc__realloc, xcpc__strdup, xcpc__fopen, xcpc__fdopen, xcpc__freopen, xcpc__open, xcpc__push_remove, xcpc__mmap, xcpc__write, xcpc__read, xcpc__fwrite, xcpc__fread, xcpc__stat, xcpc__fstat, xcpc__mkdir, xcpc__rmdir, xcpc__remove, xcpc__opendir, xcpc__pipe, xcpc__socket, xcpc__bind, xcpc__connect, xcpc__truncate, xcpc__ftruncate, xcpc__lseek
#include <libxcpc.h> void xcpc_set_exitproc(void (*proc)(int)); void xcpc_push_tryctx(xcpc_ctx ctx, xcpc_tryctx *tctx); xcpc_tryctx *xcpc_pop_tryctx(xcpc_ctx ctx); void xcpc_do_throw(xcpc_ctx ctx, int exno, void *data); void xcpc_do_rethrow(xcpc_ctx ctx); xcpc_ctx xcpc_context_create(xcpc_ctx pctx); void xcpc_context_reparent(xcpc_ctx ctx, xcpc_ctx pctx); void xcpc_context_free(xcpc_ctx ctx); xcpc_ctx xcpc_context_parent(xcpc_ctx ctx); xcpc_ctx xcpc_context_root(xcpc_ctx ctx); xcpc_ctx xcpc_context_exhandler(xcpc_ctx ctx); xcpc_res xcpc_resource_add(xcpc_ctx ctx, void *data, void (*free)(void *)); void xcpc_resource_movehead(xcpc_res res); void xcpc_resource_movetail(xcpc_res res); void xcpc_resource_moveafter(xcpc_res res, xcpc_res rres); void xcpc_resource_movebefore(xcpc_res res, xcpc_res rres);' xcpc_ctx xcpc_resource_ctx(xcpc_res res); void *xcpc_resource_data(xcpc_res res); void xcpc_resource_del(xcpc_res res, int do_free); void xcpc_resource_reparent(xcpc_res res, xcpc_ctx pctx); void xcpc_resource_set(xcpc_res res, int do_free, void *data, void (*free)(void *)); XCPC_THROW(ctx, exno, data) XCPC_RETHROW(ctx) XCPC_TRY(ctx) XCPC_CATCH(exno) XCPC_CATCH_ANY XCPC_END_TRY XCPC_RETURNV(expr) XCPC_RETURN XCPC_EXCEPT_DATA XCPC_EXCEPTION
#include <libxcpc.h> void *xcpc__malloc(xcpc_ctx ctx, xcpc_res *pres, long size); void *xcpc__realloc(xcpc_res res, long size); char *xcpc__strdup(xcpc_ctx ctx, xcpc_res *pres, char const *str); FILE *xcpc__fopen(xcpc_ctx ctx, xcpc_res *pres, char const *path, char const *mode); FILE *xcpc__fdopen(xcpc_ctx ctx, xcpc_res *pres, int fd, char const *mode); FILE *xcpc__freopen(xcpc_res res, char const *path, char const *mode); int xcpc__open(xcpc_ctx ctx, xcpc_res *pres, char const *path, int flags, int mode); void xcpc__push_remove(xcpc_ctx ctx, xcpc_res *pres, char const *path); void *xcpc__mmap(xcpc_ctx ctx, xcpc_res *pres, void *start, size_t length, int prot, int flags, int fd, off_t offset); ssize_t xcpc__write(xcpc_ctx ctx, int fd, const void *buf, size_t count); ssize_t xcpc__read(xcpc_ctx ctx, int fd, void *buf, size_t count); size_t xcpc__fwrite(xcpc_ctx ctx, const void *ptr, size_t size, size_t nmemb, FILE *stream); size_t xcpc__fread(xcpc_ctx ctx, void *ptr, size_t size, size_t nmemb, FILE *stream); void xcpc__stat(xcpc_ctx ctx, const char *path, struct stat *buf); void xcpc__fstat(xcpc_ctx ctx, int fd, struct stat *buf); void xcpc__mkdir(xcpc_ctx ctx, const char *path, mode_t mode); void xcpc__rmdir(xcpc_ctx ctx, const char *path); void xcpc__remove(xcpc_ctx ctx, const char *path); void *xcpc__opendir(xcpc_ctx ctx, const char *path); int xcpc__socket(xcpc_ctx ctx, int domain, int type, int protocol); void xcpc__pipe(xcpc_ctx ctx, int *fds); void xcpc__bind(xcpc_ctx ctx, int sfd, const struct sockaddr *addr, int alen); void xcpc__connect(xcpc_ctx ctx, int sfd, const struct sockaddr *addr, int alen); void xcpc__truncate(xcpc_ctx ctx, char const *path, off_t len); void xcpc__ftruncate(xcpc_ctx ctx, int fd, off_t len); off_t xcpc__lseek(xcpc_ctx ctx, int fd, off_t off, int whence);
The xcpc_ctx type rapresent a Container, by the means described above.
The xcpc_res type rapresent a Resource, by the means described above.
The xcpc_tryctx is an internal type that the caller should not care about it, for normal libxcpc usage.
Sets the exit function for the core libxcpc implementation. This is called when an exception has been thrown, and noone is handling it. It default on the exit(3) function, on system supporting it. Systems not supporting exit(3) should call void xcpc_set_exitproc(void (*proc)(int)) at the beginning of the program, before any other libxcpc is called.
This is an internal function that is used by the Exception macros. It pushes a new xcpc_tryctx context into the stack. Normal libxcpc should never have to call this function.
Like the void xcpc_push_tryctx(xcpc_ctx ctx, xcpc_tryctx *tctx) function, xcpc_tryctx *xcpc_pop_tryctx(xcpc_ctx ctx) is an internal function and should not be called by the normal user. This function removes the top of the exception stack from the stack itself.
This is the function that XCPC_THROW(ctx, exno, data) relies on to throw exceptions. The user should call XCPC_THROW(ctx, exno, data) instead of calling void xcpc_do_throw(xcpc_ctx ctx, int exno, void *data) directly.
This is the function that XCPC_RETHROW(ctx) relies on to re-throw exceptions. The user should call XCPC_RETHROW(ctx) instead of calling void xcpc_do_rethrow(xcpc_ctx ctx) directly.
Creates a Resource Container from the parent Container passed in the pctx parameter. The xcpc_ctx xcpc_context_create(xcpc_ctx pctx) function returns the new Container or throws an exception in case of errors.
As explained in the DESCRIPTION Containers form a hierarchy with each parent allowed to have many childs. The void xcpc_context_reparent(xcpc_ctx ctx, xcpc_ctx pctx) function makes the Context passed in ctx a new child of the Context passed in pctx, by detaching ctx from its previous parent. A root Context (the one whose parent is NULL) cannot be re-parented and trying to do so, will generate an Exception. Also, Contexts can be re-parented if they share the same root Context.
Frees the Context passed in the ctx parameter. The void xcpc_context_free(xcpc_ctx ctx) function frees all the Resources allocated inside ctx and also frees all the Context s that are rooted on ctx.
Returns the parent Context of ctx.
Returns the root Context of the ctx dinasty.
Returns the inner (the Top Of Stack in the handlers stack) Exception handling Context.
Adds a new Resource to the Context passed in the ctx parameter. The data parameter is the pointer to the Resource, while free() is the destructor to be called when the resource has to be freed. The function return the newly allocated resource, or throws an Exception in case of error.
Moves the Resource res at the beginning of the Resource list in its Context. Resources are removed (freed) from HEAD to TAIL.
Moves the Resource res at the end of the Resource list in its Context. Resources are removed (freed) from HEAD to TAIL.
Moves the Resource res after the Resource rres. This means that Resource res will be remove (freed) after Resource rres.
Moves the Resource res before the Resource rres. This means that Resource res will be remove (freed) before Resource rres.
Returns the Context inside which the Resource passed in the res parameter is allocated.
Returns the pointer to the Resource associated with res.
Deletes the Resource passed in the res parameter. If the do_free parameter is not zero, the corresponding Resource destructor is called (hence the Resource core data freed), otherwise the Resource is simply removed from its Container and its metadata freed). After the void xcpc_resource_del(xcpc_res res, int do_free) function returns, the Resource res is invalid.
Makes the Context passed in pctx the new parent of the Resource passed in res.
Changes the Resource res by setting the new data and free() parameters. If do_free is not zero, the previously associated data is freed using the previously associated destructor.
Throws an Exception number exno with its associated data. The ctx Context is used to backtrack and first the first Exception handler in the ctx hierarchy.
This statement is used to re-throw the currently handled Exception so to handlers in the upper layers of the hierarchy. The ctx parameter is the Context under which the current Exception happened. Trying to perform a XCPC_RETHROW(ctx) using a Context that is not the one registered in the Exception block by a previous XCPC_TRY(ctx) will generate a panic.
Opens an Exception handler block associated with the Context passed in the ctx parameter. This is how an Exception block may look like:
XCPC_TRY(ctx) { /* * Here we have some code that may throw exceptions ... */ ... } XCPC_CATCH(XCPCE_OPEN) { /* * Handle the XCPCE_OPEN exception ... */ ... } XCPC_CATCH(XCPCE_WRITE) { /* * Handle the XCPCE_WRITE exception ... */ ... } XCPC_CATCH_ANY { /* * Handle all other exceptions ... */ ... } XCPC_END_TRY;
Exception handlers can be nested inside each other. Every Resource or Context allocated inside the hierarchy rooted in ctx will be freed in case an Exception happened inside the code bound by the XCPC_TRY(ctx) block.
Follows a XCPC_TRY(ctx) statement and is used to catch the Exception passed in the exno parameter (see above example).
The code inside the handler is supposed to handle the Exception that happened in the associated XCPC_TRY(ctx) block (and down inside its call hierarchy). On exit from the code block inside the handler, the program will resume to the instruction following the closing XCPC_END_TRY statement. The handler can use the XCPC_RETHROW(ctx) statement to pass the Exception to handlers up in the call hierarchy
Handle every Exception not handled by the previous XCPC_CATCH(exno) blocks. In the same way as XCPC_CATCH(exno) the handler code can simply exit the code block, or can use XCPC_RETHROW(ctx) to pass the Exception in the upper layers of the call hierarchy.
Ends an Exception handler started with a previous XCPC_TRY(ctx) statement. If no Exception happens, or if one of the handler catch the Exception without re-throwing, the program will continue with the next Exception following the XCPC_END_TRY statement.
The code inside an Exception handler (being it TRY or CATCH) cannot simply issue a return to return from the current function. The XCPC_RETURNV(expr) statement must be used to return from the current function, with expr being the expression to be returned.
Same as XCPC_RETURNV(expr) but for void functions.
Macro that can be used to fetch the data associated with an Exception.
Macro that can be used to fetch the Exception number.
Allocates a memory block of size bytes. A new Resource will be associated with the new block of data, and the pres pointer will receive its value. The new Resource will be stored inside the Context passed in the ctx parameter. The new block pointer will be returned by the function. In case of error, an Exception will be thrown.
Realloc a Resource previously allocated with void *xcpc__malloc(xcpc_ctx ctx, xcpc_res *pres, long size) to the new size size. The reallocated block will be associated with the same Resource res and the function will return it. An Exception is thrown in case of error.
Makes a copy of the input string str and links the allocated Resource inside the ctx Context. Returns the newly allocated string copy, or throws an Exception in case of error.
Opens a new file using the fopen(3) function and links it to a new Resource that will be stored in the pres parameter. Returns the newly opened FILE pointer, or throws an Exception in case of error.
Opens a new stream file using the fdopen(3) function. Returns the newly opened FILE pointer, or throws an Exception in case of error.
Opens a new stream file using the freopen(3) function. Returns the newly opened FILE pointer, or throws an Exception in case of error.
Opens a new file descriptor using the open(2) function. Returns the newly opened file descriptor pointer, or throws an Exception in case of error.
This is an example about a Resource that does not have any real payload, but its used only to leverage the cleanup capabilities of the Resource destructors. It creates a Resource that whose cleanup will trigger the removal of the file whose path is passed in path.
Creates a new memory mapping using the mmap(2) function. Returns the newly created mapping address, or throws an Exception in case of error.
Maps to the write(2) function and throws an Exception if the number of bytes written are different from count.
Maps to the read(2) function and throws an Exception if the number of bytes read are different from count.
Maps to the fwrite(3) function and throws an Exception if the number of elements written are different from nmemb.
Maps to the fread(3) function and throws an Exception if the number of elements read are different from nmemb.
Maps to the stat(2) function and throws an Exception in case of error.
Maps to the fstat(2) function and throws an Exception in case of error.
Maps to the mkdir(2) function and throws an Exception in case of error.
Maps to the rmdir(2) function and throws an Exception in case of error.
Maps to the remove(3) function and throws an Exception in case of error.
Maps to the opendir(3) function and throws an Exception in case of error.
Maps to the pipe(2) function and throws an Exception in case of error.
Maps to the socket(2) function and throws an Exception in case of error.
Maps to the bind(2) function and throws an Exception in case of error.
Maps to the connect(2) function and throws an Exception in case of error.
Maps to the truncate(2) function and throws an Exception in case of error.
Maps to the ftruncate(2) function and throws an Exception in case of error.
Maps to the lseek(2) function and throws an Exception in case of error.
int file_copy1(char const *src, char const *dst) { int sfd, dfd; size_t count, rdy; char *buf; struct stat stb; if ((sfd = open(src, O_RDONLY)) == -1) return -1; if (fstat(sfd, &stb)) { close(sfd); return -2; } if ((dfd = open(dst, O_WRONLY | O_CREAT)) == -1) { close(sfd); return -3; } if ((buf = malloc(BSIZE)) == NULL) { close(dfd); close(sfd); return -4; } for (count = 0; count < stb.st_size;) { if ((rdy = stb.st_size - count) > BSIZE) rdy = BSIZE; if (read(sfd, buf, rdy) != rdy || write(dfd, buf, rdy) != rdy) { free(buf); close(dfd); close(sfd); return -5; } count += rdy; } free(buf); close(dfd); close(sfd); return 0; }
Another style example for coding the same function is:
int file_copy2(char const *src, char const *dst) { int err, sfd, dfd; size_t count, rdy; char *buf; struct stat stb; err = -1; if ((sfd = open(src, O_RDONLY)) == -1) goto err_1; err = -2; if (fstat(sfd, &stb)) goto err_2; err = -3; if ((dfd = open(dst, O_WRONLY | O_CREAT)) == -1) goto err_2; err = -4; if ((buf = malloc(BSIZE)) == NULL) goto err_3; for (count = 0; count < stb.st_size;) { if ((rdy = stb.st_size - count) > BSIZE) rdy = BSIZE; err = -5; if (read(sfd, buf, rdy) != rdy || write(dfd, buf, rdy) != rdy) goto err_4; count += rdy; } err = 0; err_4: free(buf); err_3: close(dfd); err_2: close(sfd); err_1: return err; }
Let's see how it looks using the libxcpc library:
int file_copy3(xcpc_ctx ctx, char const *src, char const *dst) { xcpc_ctx wctx; int sfd, dfd; size_t count, rdy; char *buf; struct stat stb; wctx = xcpc_context_create(ctx); sfd = xcpc__open(wctx, NULL, src, O_RDONLY, 0); xcpc__fstat(wctx, sfd, &stb); dfd = xcpc__open(wctx, NULL, dst, O_WRONLY | O_CREAT, 0644); buf = xcpc__malloc(wctx, NULL, BSIZE); for (count = 0; count < stb.st_size;) { if ((rdy = stb.st_size - count) > BSIZE) rdy = BSIZE; xcpc__read(wctx, sfd, buf, rdy); xcpc__write(wctx, dfd, buf, rdy); count += rdy; } xcpc_context_free(wctx); return err; }
The code using file_copy3(), or using code that uses file_copy3(), will can then handle the exceptions in the proper place, like:
XCPC_TRY(ctx) { file_copy3(ctx, ...); } XCPC_CATCH(XCPCE_OPEN) { ... } XCPC_CATCH(XCPCE_READ) { ... } XCPC_CATCH_ANY { ... } XCPC_END_TRY;
The Pros of the Exception handling code against the in function error handling, is that you can handle exception wherever it makes sense for your program. In the simplest case, a program could have a single Exception block in the main() function, and handle everything in there.
Compared to C++ Exception handling, libxcpc performance is very good. You can compare yourself using the xcpc_bench binary inside the test subdirectory, against the analoguous bench program at:
http://www.xmailserver.org/cpp-exbench.cpp
In my machine, libxcpc performs almost ten times faster than standard C++ Exception handling in the throwing case. While GCC Exception handling is about four times faster than libxcpc Exception handling in the non throwing case. Those are micro-benchmarks though, and the effective cost of the libxcpc Exception handling is negligible when used in software does does some real work besides calling XCPC_TRY(ctx).
When an Exception block is opened using XCPC_TRY(ctx) all the new Resources and Contexts that are rooted to the Context ctx will be freed in case of Exception. If a new Resource or Context is allocated on a Context that is not rooted in ctx, they will not be freed by the Excption handling mechanism (unless the handler does not handle the Exception or re-throws, and the upper layer handle uses a Context that is root for the allocated Resources or Contexts).
The libxcpc library is re-entrant by nature, since it does not use any writeable global variable. The libxcpc is thread-safe as long as two different threads do not work at the same Context hierarchy at the same time. It is perfetcly legal to allocated a Context hierarchy in one thread and pass it to another one. As long as two threads do not use it at the same time.
http://www.gnu.org/copyleft/lesser.html
http://www.xmailserver.org/libxcpc-lib.html