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,847
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

/* 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);
}

Offline

#4 2013-12-11 10:19 PM

RobSeace
Administrator
From: Boston, MA
Registered: 2002-06-12
Posts: 3,847
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

/* 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);
   }
}

Offline

#6 2013-12-12 10:26 PM

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

Re: Error handling

Offline

  • Index
  • » C
  • » Error handling

Board footer

Powered by FluxBB