UNIX Socket FAQ

A forum for questions and answers about network programming on Linux and all other Unix-like systems

You are not logged in.

  • Index
  • » C
  • » Error handling

#1 2013-12-10 08:21 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Error handling

Hi Gurus

Just a quick one, if you guys where developing a libary of some kind (for instance something that wraps the socket api quite nicely) what's the best logical way of handling and return errors to the caller, let me try and explain further, mostly everything in the libary uses system calls which effectively sets errno on failure, so do you simply mimic what the system calls do I.e return -1 or whatever value and create a fuction which performs something similar to perror... Something like this terrible example

#define ERRSTR_LEN 2048
#define ERRNO -1 /* With errno */
#define NOERR -2 /* Without errno */

static char g_errstr[ERRSTR_LEN];

/* Our libary functions call this on error */
void nc_set_error (int error_flag, const char *format, ...)
{
   int errno_save, len;
   va_list ap;
   
   va_start(ap, format);
   vsnprintf(g_errstr, ERRSTR_LEN, format, ap);
   va_end(ap);
   
   if (error_flag >= ERRNO)
   {  /* Do we set errno ?  */
      if (error_flag > ERRNO)
         errno = error_flag;
      
      errno_save = errno;
      len = strlen(g_errstr);
      
      snprintf(g_errstr + len, ERRSTR_LEN - len, " :%s", strerror(error_save));
   }
   
   return;
}

/* caller calls this for an error string */
char *nc_get_error (void)
{
   char *p = g_errstr;
   
   return (p);
}

Or do you completely write your own errors for everything?

Looking for the correct way (reassurance) ;)

Offline

#2 2013-12-10 10:12 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,826
Website

Re: Error handling

It depends...  For smaller/low-level lib functions, I'll often go with having them just set errno appropriately and let the caller treat them like any other syscall-based libc function...  This works best when your lib function is calling other syscalls which set errno for you appropriately, and/or your own internal failure conditions map nicely onto existing errno values...  For more complex stuff which has failure conditions that don't easily map onto errno values, then I go with my own error codes and/or strings...

For something that's meant to cover socket functions, you'll definitely want more than simple errno handling, at least...  In addition to errno failures, you'll have to deal with either h_errno failures (from gethostbyname() and friends) or getaddrinfo() return value error codes (fed to gai_strerror() to obtain a string version)...  If you're wrapping SSL handling in as well, then that's another group of error codes/strings to deal with...  If supporting Windows, you'll need to deal with their wacky WSAGetLastError() crap...  So, you probably want a flexible system which can store not only a simple code value but a value identifying what type that code is, and then your error-code-to-string function can use that type to determine how to convert the raw code value to a string...

Alternatively, you can eliminate all concept of error codes from your callers' point of view by only giving them error strings for everything...  Then, the decision of what code-to-string function to call can be made in your internal lib code at the point where each error occurs, rather than need to keep track of the type so you can defer the string conversion for later...  This also gives you easy flexibility to add arbitrary error conditions with no corresponding codes to handle various internal errors...  (Of course, with the other method, you'd just need to define your own internal code type and values to do the same thing...)  For most callers, that's all they're going to really care about, anyway: a printable/loggable string error message...

Offline

#3 2013-12-11 03:53 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Error handling

Robseace wrote:

For something that's meant to cover socket functions, you'll definitely want more than simple errno handling, at least...  In addition to errno failures, you'll have to deal with either h_errno failures (from gethostbyname() and friends) or getaddrinfo() return value error codes (fed to gai_strerror() to obtain a string version)...  If you're wrapping SSL handling in as well, then that's another group of error codes/strings to deal with...

Exactly what I was thinking... so I've gone with your second approach, i.e something like this.

/* error.h */
#ifndef _error_h
#define _error_h

#define ERROR_STRLEN 2084

#define ERROR_TYPE_SYS 1 /* Errno */
#define ERROR_TYPE_GAI 2 /* getaddrinfo() */
#define ERROR_TYPE_LIB 3 /* Netcore */

/* Define our own lib errors here... */

struct nc_error_t
{
	int type;
	int code;
	char string[ERROR_STRLEN];
};

#endif

/* error.c */
#include "netcore.h"
#include "error.h"

static struct nc_error_t g_nc_error;

void nc_set_error (int type, int code, const char *format, ...)
{
	struct nc_error_t *ep = &g_nc_error;
	int len = 0;
	
	ep->type = type;
	ep->code = code;
	
	if (format != NULL && *format != '\0')
	{
		va_list ap;
		
		va_start(ap, format);
		vsnprintf(ep->string, ERROR_STRLEN, format, ap);
		va_end(ap);
		
		len = strlen(ep->string);
	}
	
	switch (ep->type)
	{
		case ERROR_TYPE_SYS:
		{
			snprintf(ep->string + len, ERROR_STRLEN - len, " --> %s\n", strerror(ep->code));
			break;
		}
		
		case ERROR_TYPE_GAI:
		{
			snprintf(ep->string + len, ERROR_STRLEN - len, " --> %s\n", gai_strerror(ep->code));
			break;
		}
		
		case ERROR_TYPE_LIB:
		{
			/* Whatever lib errors we come up with... */
		}
		
		default:
		{
			snprintf(ep->string + len, ERROR_STRLEN - len, " --> Unknown error condition!\n");
			break;
		}
	}
	
	return;
}

char *nc_get_error_string (void)
{
	char *p = g_nc_error.string;
	
	return (p);
}

And used in our libary functions like so...

#include "netcore.h"
#include "error.h"

struct addrinfo *nc_lookup (const char *node, const char *service, int family, int socktype)
{
	struct addrinfo hints, *result;
	int gai_error;
	
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = socktype;
	hints.ai_family   = family;
	
	if (node != NULL && *node != '\0')
		hints.ai_flags = AI_CANONNAME;
	else
		hints.ai_flags = AI_PASSIVE;	/* INADDR_ANY */
		
	if ((gai_error = getaddrinfo(node, service, &hints, &result)) != 0)
	{
		nc_set_error(ERROR_TYPE_GAI, gai_error, "lookup failure!");
		return (NULL);
	}
	
	return (result);
}

Offline

#4 2013-12-11 10:19 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,826
Website

Re: Error handling

Since it looks like you're only allowing callers to see the string message, not the codes, then this really isn't any functionally different from the final option I mentioned, just with the added overhead of keeping track of the codes and types unnecessarily...  Personally, I'd get rid of the whole nc_set_error() function, and just do snprintf()'s directly into a static/global buffer at every error point...  Or, at least simplify it so that it is just a direct cover for vsnprintf() into the static buffer, and let the callers do strerror() or gai_strerror() as appropriate (they already know what's needed!) and format the results into their message in their own ways as desired, rather than force always cramming the code error string at the end in some fixed format...  Eg., for your given example:

if ((gai_error = getaddrinfo(node, service, &hints, &result)) != 0)
{
    nc_set_error ("Failed to lookup (%s:%s): %s", node, service, (gai_error == EAI_SYSTEM) ? strerror (errno) : gai_strerror (gai_error));
    return (NULL);
}

Note the EAI_SYSTEM check, which you probably also want since in that case the actual meaningful message will be in errno...  If calling getaddrinfo() (or getnameinfo()) multiple places, you probably just want to turn that final ?: conditional into a macro you can call to simplify things...

But, one other thing to consider: if you're going to be using a single global/static error message buffer, that means your library can't really be used by multithreaded apps...  You could make the error message buffer use thread-local storage or something, I suppose...  Or, what I'd probably do is define some handle structure containing all necessary info about a socket and have that contain a per-socket error buffer...  You'd have one new/init type function that creates and returns a pointer to one of these handle structs, then all other functions would take that handle pointer for working with...  But, then, my idea for a sockets cover library is probably more grand than what you're shooting for; I would defnitely want to have transparent SSL support for one, which requires keeping track of an "SSL*" pointer in addition to the raw socket FD, so some manner of wrapper handle struct would be required anyway...

Offline

#5 2013-12-12 04:03 PM

RipRage
Member
From: England
Registered: 2010-01-06
Posts: 146

Re: Error handling

RobSeace wrote:

Note the EAI_SYSTEM check

Noted! I forgot about getaddrinfo() returning EAI_SYSTEM

But, one other thing to consider: if you're going to be using a single global/static error message buffer, that means your library can't really be used by multithreaded apps...

Yes this is something I was considering, and you have mentioned a nice way of handling it here...

Or, what I'd probably do is define some handle structure containing all necessary info about a socket and have that contain a per-socket error buffer..

To be honest with you I have no idea how handles are implemented, its not something you come across in a textbook (how to implement them anyway) from what books I have here on the shelf, so I can only guess from what experience i have from using them that they are implemented something like this perhaps.... using the same code above;

/* This defined somewhere in a header file */
#typedef void *SOCKET_HANDLE;

/* This hidden in a C file somewhere Per socket information */
struct socket_info_t
{
   struct addrinfo *lookup_info; /* Lookup information */
   int sockfd; /* Socket */
   int status; /* Blocking vs Non-Blocking ? */
   char error[ERROR_STRLEN]; /* Error string */
};

/* Three functions using the handle */
SOCKET_HANDLE NC_initialize (void)
{
   struct socket_info_t *sk_info;
	
   sk_info = (struct socket_info_t *) calloc(1, sizeof(struct socket_info_t));
   if (sk_info == NULL)
	return (NULL);
	
   return (sk_info);
}

int NC_lookup (const char *node, const char *service, int family, int socktype, SOCKET_HANDLE *sk_hand)
{
   struct socket_info_t *sk_info;
   struct addrinfo hints;
	
   int gai_error;
	
   /* Not sure how to handle sk_hand == NULL ? */
   asset(sk_hand != NULL);
	
   sk_info = (struct socket_info_t *) sk_hand;
	
   memset(&hints, 0, sizeof(hints));
   hints.ai_socktype = socktype;
   hints.ai_family   = family;
	
   if (node != NULL && *node != '\0')
	hints.ai_flags = AI_CANONNAME;
   else
	hints.ai_flags = AI_PASSIVE;
	
   if ((gai_error = getaddrinfo(node, service, &hints, &sk_info->lookup_info)) != 0)
   {
      snprintf(sk_info->error, ERROR_STRLEN, "Failed to lookup: (%s %s): %s\n",
		  node, service, (gai_error == EAI_SYSTEM) ? strerror(errno) : gai_strerror(gai_error));
	
      return (-1);
   }
	
   return (0);
}

char *NC_error (SOCKET_HANDLE *sk_hand)
{
   struct socket_info_t *sk_info;
	
   asset(sk_hand != NULL);
	
   sk_info = (struct socket_info_t *) sk_hand;
	
   return (sk_info->error);
}

/* And main simply looks something like this... */
int main()
{
   SOCKET_HANDLE sk_hand;
	
   if ((sk_hand = NC_initialize()) == NULL)
   {
      perror("Netcore failed to initialize!");
      exit(1);
   }
	
   if (NC_lookup("www.google.com", "80", AF_UNSPEC, SOCK_STREAM, &sk_hand) != 0)
   {
      fprintf(stderr, "NC_lookup() %s\n", NC_error(&sk_hand));
      return (-1);
   }
}

But, then, my idea for a sockets cover library is probably more grand than what you're shooting for

Well that's not necessary true... I do often come up with cool funky ideas by myself, but there's one big difference, your a programming god (so you can simply think of something and know how to implement it) where as I, well I'm just a hungry boy scout trying to work my way up to the top ;)

However, the library will be for my own use, I would like another application I'm working on to work of it, I spoke to some of the developers working on Elementary OS and they think its a really cool idea for a network diagnosis / auto-fix application (which I am naming "Autonetix" :) so I will quietly work on it, (whether or not it will actually become something is another story) but it gives me something to put my heart ans soul into and also something to practice with... :)

Offline

#6 2013-12-12 10:26 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,826
Website

Re: Error handling

To be honest with you I have no idea how handles are implemented, its not something you come across in a textbook (how to implement them anyway) from what books I have here on the shelf, so I can only guess from what experience i have from using them that they are implemented something like this perhaps....

Yeah, that's pretty much the way to do it, if you want to hide the actual handle contents from poking at by external callers anyway...  The alternative is to just expose the actual handle struct to callers and do away with the casting back and forth...  But, if you do that, you're pretty much committed to supporting that struct layout and can't just change it on a whim (without going to a new major version of your library)...  I sometimes use the hidden void* approach, but other times use the fully exposed handle contents approach if I feel callers (which are usually also myself) may need access to the internals...  In the latter case, I'll often throw in some extra padding space to allow adding new fields in the future without breaking ABI by just stealing some of the padding...  Or else design it to be extensible in some other way, like have a pointer to a list of arbitrary settings/extensions which can then be grown to any arbitray length needed without changing the size of the main handle struct...

But, for something like this, there's probably little need for callers to be poking around in it, so yeah hiding it behind void* is probably the way to go...  For any bits of data they might want to extract from it (the error message string, the raw socket FD, etc.), you can just have simple cover functions that return it...

But, just as a stylistic thing, I usually like to define all functions to take the handle as the first argument rather than the last...  It doesn't really matter, but I think it looks cleaner that way...

I'd also allow some method of letting the callers specify other getaddrinfo() hint flags (like AI_ADDRCONFIG, AI_V4MAPPED, AI_ALL, AI_IDN, etc.)...  You could just add a "flags" arg that lets those be passed in directly, but that is somewhat ugly in that it gives away your internal implementation as based around getaddrinfo(), and it requires the system actually supports it...  But, the alternative of creating your own set of duplicate flags isn't much nicer, so I might embrace the ugliness and just use the system AI_* flags directly...  Maybe define a macro with sane defaults for client or server use, so callers don't need to care to look into the details of getaddrinfo() if they don't have a need for special features...  (In addition to the ones you use, I'd throw in AI_ADDRCONFIG by default, too...)

Offline

  • Index
  • » C
  • » Error handling

Board footer

Powered by FluxBB