error handling magic
authorTero Marttila <terom@fixme.fi>
Sun, 22 Feb 2009 06:44:16 +0200
changeset 3 cc94ae754e2a
parent 2 a834f0559939
child 4 a3ca0f97a075
error handling magic
src/error.h
src/sock.c
src/sock.h
src/sock_gnutls.c
src/sock_gnutls.h
src/sock_internal.h
src/sock_tcp.c
src/sock_tcp.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/error.h	Sun Feb 22 06:44:16 2009 +0200
@@ -0,0 +1,76 @@
+#ifndef ERROR_H
+#define ERROR_H
+
+/*
+ * Error-handling functions
+ */
+#include <errno.h>
+
+/*
+ * Type used for error codes is an explicitly *unsigned* int, meaning that error codes themselves are positive.
+ * Negative error codes also exist in some places, and they are just a negative err_t.
+ */
+typedef unsigned int err_t;
+
+/*
+ * List of defined error codes, organized mostly by function name
+ */
+enum error_code {
+    ERR_CALLOC                  = 0x000100,
+    
+    // network resolver errors
+    ERR_GETADDRINFO             = 0x000200,
+    ERR_GETADDRINFO_EMPTY       = 0x000201,     /* No valid results */
+
+    // low-level network errors
+    ERR_SOCKET                  = 0x000301,
+    ERR_CONNECT                 = 0x000302,
+
+    // low-level IO errors
+    ERR_READ                    = 0x000401,
+    ERR_WRITE                   = 0x000402,
+};
+
+/*
+ * An error code and associated extra infos
+ */
+struct error_info {
+    /* The base error code */
+    err_t code;
+
+    /* Additional detail info, usually some third-part error code */
+    unsigned int extra;
+};
+
+/** No error, evaulates as logical false */
+#define SUCCESS (0)
+
+/* Evaulates to error_info.code as lvalue */
+#define ERROR_CODE(err_info) ((err_info).code)
+
+/* Evaulates to error_info.extra as lvalue */
+#define ERROR_EXTRA(err_info) ((err_info).extra)
+
+/* Set error_info.code to SUCCESS, evaulates as zero */
+#define RESET_ERROR(err_info) ((err_info).code = SUCCESS)
+
+/* Compare error_info.code != 0 */
+#define IS_ERROR(err_info) (!!(err_info).code)
+
+/* Set error_info.code, but leave err_extra as-is. Evaluates to err_code */
+#define SET_ERROR(err_info, err_code) ((err_info).code = (err_code))
+
+/* Set error_info.code/extra. XXX: should evaluate to err_code */
+#define _SET_ERROR_EXTRA(err_info, err_code, err_extra) (err_info).code = (err_code); err_info.extra = (err_extra)
+#define SET_ERROR_EXTRA(err_info, err_code, err_extra) do { _SET_ERROR_EXTRA(err_info, err_code, err_extra); } while (0)
+
+/* Set error.info.code to err_code, and .extra to errno. XXX: should evaulate to err_code */
+#define _SET_ERROR_ERRNO(err_info, err_code) _SET_ERROR_EXTRA(err_info, err_code, errno);
+#define SET_ERROR_ERRNO(err_info, err_code) SET_ERROR_EXTRA(err_info, err_code, errno);
+
+/* Ss above, but also return err_code from func. XXX: use 'return SET_ERROR...' instead */
+#define RETURN_SET_ERROR(err_info, err_code) do { _SET_ERROR(err_info, err_code); return (err_code); } while (0)
+#define RETURN_SET_ERROR_EXTRA(err_info, err_code, err_extra) do { _SET_ERROR_EXTRA(err_info, err_code, err_extra); return (err_code); } while (0)
+#define RETURN_SET_ERROR_ERRNO(err_info, err_code) do { _SET_ERROR_ERRNO(err_info, err_code); return (err_code); } while (0)
+
+#endif
--- a/src/sock.c	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock.c	Sun Feb 22 06:44:16 2009 +0200
@@ -2,21 +2,41 @@
 #include "sock_internal.h"
 #include "sock_gnutls.h"
 
-void sock_init (void)
+#include <assert.h>
+
+err_t sock_init (void)
 {
-    // XXX: just call directly for now
-    sock_gnutls_init();
+    err_t err;
+
+    // XXX: just call these all directly for now
+
+    if ((err = sock_gnutls_init()))
+        return err;
 }
 
-int sock_stream_read (struct sock_stream *sock, void *buf, size_t len)
+void sock_stream_init (struct sock_stream *sock, struct sock_stream_type *type)
+{
+    // be strict
+    assert(sock->type == NULL);
+
+    // store type
+    sock->type = type;
+}
+
+err_t sock_stream_read (struct sock_stream *sock, void *buf, size_t len)
 {
     // proxy off to method handler
     return sock->type->methods.read(sock, buf, len);
 }
 
-int sock_stream_write (struct sock_stream *sock, const void *buf, size_t len)
+err_t sock_stream_write (struct sock_stream *sock, const void *buf, size_t len)
 {
     // proxy off to method handler
     return sock->type->methods.write(sock, buf, len);
 }
 
+void sock_stream_error (struct sock_stream *sock, struct error_info *err)
+{
+    // copy from SOCK_ER
+    *err = SOCK_ERR(sock);
+}
--- a/src/sock.h	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock.h	Sun Feb 22 06:44:16 2009 +0200
@@ -4,6 +4,7 @@
 /*
  * Low-level socket-related functions
  */
+#include "error.h"
 #include <sys/types.h>
 
 /*
@@ -12,14 +13,19 @@
 struct sock_stream;
 
 /*
- * A simple blocking TCP connect to the given host/service, using getaddrinfo.
+ * Initialize the socket module's global state. Call this before calling any other sock_* functions.
+ */
+err_t sock_init (void);
+
+/*
+ * A simple blocking TCP connect to the given host/service, using getaddrinfo. The connected socket is returned via
+ * *sock_ptr. In case of errors, additional error information is stored in *err
+ *
+ * @return zero on success, nonzero on error
  *
  * XXX: blocking
- * XXX: exits on error
- *
- * Returns the socket handle.
  */
-struct sock_stream *sock_tcp_connect (const char *host, const char *service);
+err_t sock_tcp_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err);
 
 /*
  * A simple blocking SSL connect to the given host/service.
@@ -28,19 +34,19 @@
  * XXX: doesn't do any certificate verification.
  * XXX: exits on error
  *
- * Returns the socket handle.
+ * Returns the socket handle, or NULL on errors.
  */
 struct sock_stream *sock_ssl_connect (const char *host, const char *service);
 
 /*
- * Initialize the socket module's global state
- */
-void sock_init (void);
-
-/*
  * The generic read/write API for stream sockets.
  */
-int sock_stream_read (struct sock_stream *sock, void *buf, size_t len);
-int sock_stream_write (struct sock_stream *sock, const void *buf, size_t len);
+err_t sock_stream_read (struct sock_stream *sock, void *buf, size_t len);
+err_t sock_stream_write (struct sock_stream *sock, const void *buf, size_t len);
+
+/**
+ * Get last err_info for \a sock, returned via \a *err.
+ */
+void sock_stream_error (struct sock_stream *sock, struct error_info *err);
 
 #endif
--- a/src/sock_gnutls.c	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock_gnutls.c	Sun Feb 22 06:44:16 2009 +0200
@@ -12,7 +12,7 @@
         errx(1, "%s: %s", func, gnutls_strerror(_err));
 }
 
-static int sock_gnutls_read (struct sock_stream *base_sock, void *buf, size_t len)
+static err_t sock_gnutls_read (struct sock_stream *base_sock, void *buf, size_t len)
 {
     struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
     
@@ -20,7 +20,7 @@
     return gnutls_record_recv(sock->session, buf, len);
 }
 
-static int sock_gnutls_write (struct sock_stream *base_sock, const void *buf, size_t len)
+static err_t sock_gnutls_write (struct sock_stream *base_sock, const void *buf, size_t len)
 {
     struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
     
@@ -51,8 +51,7 @@
       gnutls_certificate_allocate_credentials(&ctx->xcred);
 }
 
-// XXX: errors
-void sock_gnutls_init (void)
+err_t sock_gnutls_init (void)
 {
     int _err;
 
@@ -62,6 +61,9 @@
 
     // init _sock_gnutls_ctx
     sock_gnutls_client_ctx_anon(&_sock_gnutls_client_ctx);
+
+    // done
+    return SUCCESS;
 }
 
 
--- a/src/sock_gnutls.h	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock_gnutls.h	Sun Feb 22 06:44:16 2009 +0200
@@ -35,10 +35,11 @@
 
 #define SOCK_GNUTLS_BASE(sock_ptr) (&(sock_ptr)->base_tcp.base)
 #define SOCK_GNUTLS_TCP(sock_ptr) (&(sock_ptr)->base_tcp)
+#define SOCK_GNUTLS_ERR(sock_ptr) SOCK_ERR(SOCK_GNUTLS_BASE(sock_ptr))
 
 /*
  * Initialize the global gnutls state
  */
-void sock_gnutls_init (void);
+err_t sock_gnutls_init (void);
 
 #endif /* SOCK_GNUTLS_H */
--- a/src/sock_internal.h	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock_internal.h	Sun Feb 22 06:44:16 2009 +0200
@@ -10,10 +10,10 @@
     /* method table */
     struct sock_stream_methods {
         /* Normal read(2) */
-        int (*read) (struct sock_stream *sock, void *buf, size_t len);
+        err_t (*read) (struct sock_stream *sock, void *buf, size_t len);
 
         /* Normal write(2) */
-        int (*write) (struct sock_stream *sock, const void *buf, size_t len);
+        err_t (*write) (struct sock_stream *sock, const void *buf, size_t len);
 
     } methods;
 };
@@ -25,9 +25,21 @@
  * as appropriate.
  */
 struct sock_stream {
+    /* The sock_stream_type for this socket */
     struct sock_stream_type *type;
+
+    /* Last error info */
+    struct error_info err;
 };
 
 #define SOCK_FROM_BASE(sock, type) ((type*) sock)
+#define SOCK_ERR(sock) ((sock)->err)
+
+/*
+ * Initialize a sock_stream with the given sock_stream_type.
+ *
+ * The sock_stream should be initialized to zero. It is a bug to call this twice.
+ */
+void sock_stream_init (struct sock_stream *sock, struct sock_stream_type *type);
 
 #endif /* SOCK_INTERNAL_H */
--- a/src/sock_tcp.c	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock_tcp.c	Sun Feb 22 06:44:16 2009 +0200
@@ -7,66 +7,82 @@
 #include <netdb.h>
 #include <unistd.h>
 #include <string.h>
-#include <err.h>
+#include <assert.h>
 
 /*
- * Our sock_stream_type.methods.read implementation
+ * Our sock_stream_methods.read method
  */
-static int sock_tcp_read (struct sock_stream *base_sock, void *buf, size_t len)
+static err_t sock_tcp_read (struct sock_stream *base_sock, void *buf, size_t len)
 {
     struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
+    int ret;
+    
+    // map directly to read(2)
+    if ((ret = read(sock->fd, buf, len)) < 0)
+        // errno
+        RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_READ);
 
-    return read(sock->fd, buf, len);
+    else 
+        // bytes read
+        return ret;
 }
 
 /*
- * Our sock_stream_type.methods.write implementation
+ * Our sock_stream_methods.write method
  */
-static int sock_tcp_write (struct sock_stream *base_sock, const void *buf, size_t len)
+static err_t sock_tcp_write (struct sock_stream *base_sock, const void *buf, size_t len)
 {
     struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
+    int ret;
+    
+    // map directly to write(2)
+    if ((ret = write(sock->fd, buf, len)) < 0)
+        // errno
+        RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_WRITE);
 
-    return write(sock->fd, buf, len);
+    else
+        // bytes read
+        return ret;
 }
 
 /*
  * Our sock_stream_type
- *
- * XXX: move to sock_tcp.h
  */
 struct sock_stream_type sock_tcp_type = {
     .methods.read   = &sock_tcp_read,
     .methods.write  = &sock_tcp_write,
 };
 
-struct sock_tcp* sock_tcp_alloc (void)
+err_t sock_tcp_alloc (struct sock_tcp **sock_ptr)
 {
-    struct sock_tcp *sock;
-
     // alloc
-    if ((sock = calloc(1, sizeof(*sock))) == NULL)
-        errx(1, "calloc");
+    if ((*sock_ptr = calloc(1, sizeof(**sock_ptr))) == NULL)
+        return ERR_CALLOC;
     
-    // initialize base
-    sock->base.type = &sock_tcp_type;
+    // initialize base with sock_tcp_type
+    sock_stream_init(SOCK_TCP_BASE(*sock_ptr), &sock_tcp_type);
 
     // done
-    return sock;
+    return SUCCESS;
 }
 
-int sock_tcp_init_fd (struct sock_tcp *sock, int fd)
+err_t sock_tcp_init_fd (struct sock_tcp *sock, int fd)
 {
+    // valid fd -XXX: err instead?
+    assert(fd >= 0);
+
     // initialize
     sock->fd = fd;
 
     // done
-    return 0;
+    return SUCCESS;
 }
 
-int sock_tcp_init_connect (struct sock_tcp *sock, const char *hostname, const char *service)
+err_t sock_tcp_init_connect (struct sock_tcp *sock, const char *hostname, const char *service)
 {
     struct addrinfo hints, *res, *r;
-    int _err;
+    int err;
+    RESET_ERROR(SOCK_TCP_ERR(sock));
     
     // hints
     memset(&hints, 0, sizeof(hints));
@@ -74,40 +90,85 @@
     hints.ai_socktype = SOCK_STREAM;
 
     // resolve
-    if ((_err = getaddrinfo(hostname, service, &hints, &res)))
-        errx(1, "getaddrinfo: %s", gai_strerror(_err));
+    if ((err = getaddrinfo(hostname, service, &hints, &res)))
+        RETURN_SET_ERROR_EXTRA(SOCK_TCP_ERR(sock), ERR_GETADDRINFO, err);
 
-    // use
+    // try each result in turn
     for (r = res; r; r = r->ai_next) {
-        // XXX: wrong
-        if ((sock->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0)
-            err(1, "socket");
+        // create the socket
+        if ((sock->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0) {
+            // remember error
+            SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_SOCKET);
 
-        if (connect(sock->fd, r->ai_addr, r->ai_addrlen))
-            err(1, "connect");
+            // skip to next one
+            continue;
+        }
+        
+        // connect to remote address
+        if (connect(sock->fd, r->ai_addr, r->ai_addrlen)) {
+            // remember error
+            SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_CONNECT);
+            
+            // close/invalidate socket
+            close(sock->fd);
+            sock->fd = -1;
 
+            // skip to next one
+            continue;
+        }
+        
+        // valid socket, use this
         break;
     }
     
-    // ensure we got some valid socket
-    if (sock->fd < 0)
-        errx(1, "no valid socket");
+    // ensure we got some valid socket, else return last error code
+    if (sock->fd < 0) {
+        // did we hit some error?
+        if (IS_ERROR(SOCK_TCP_ERR(sock)))
+            // return last error
+            return ERROR_CODE(SOCK_TCP_ERR(sock));
+        
+        else
+            // no results
+            return SET_ERROR(SOCK_TCP_ERR(sock), ERR_GETADDRINFO_EMPTY);
+    }
     
     // ok, done
     return 0;    
 }
 
-// XXX: error handling
-struct sock_stream* sock_tcp_connect (const char *host, const char *service) 
+err_t sock_tcp_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err_info)
 {
     struct sock_tcp *sock;
+    err_t err;
     
     // allocate
-    sock = sock_tcp_alloc();
+    if ((err = sock_tcp_alloc(&sock)))
+        return err;
 
     // connect
-    sock_tcp_init_connect(sock, host, service);
+    if ((err = sock_tcp_init_connect(sock, host, service))) {
+        // set *err_info
+        *err_info = SOCK_TCP_ERR(sock);
 
-    // done
-    return SOCK_TCP_BASE(sock);
+        // cleanup
+        sock_tcp_release(sock);
+        
+        // return error code
+        return err;
+    }
+
+    // good
+    *sock_ptr = SOCK_TCP_BASE(sock);
+
+    return 0;
 }
+
+void sock_tcp_release (struct sock_tcp *sock)
+{
+    // must not be connected
+    assert(sock->fd < 0);
+
+    // free
+    free(sock);
+}
--- a/src/sock_tcp.h	Sun Feb 22 05:27:29 2009 +0200
+++ b/src/sock_tcp.h	Sun Feb 22 06:44:16 2009 +0200
@@ -18,20 +18,26 @@
 };
 
 #define SOCK_TCP_BASE(sock_ptr) (&(sock_ptr)->base)
+#define SOCK_TCP_ERR(sock_ptr) SOCK_ERR(SOCK_TCP_BASE(sock_ptr))
 
 /*
  * Allocate a new blank sock_tcp with a correctly initialized base
  */
-struct sock_tcp* sock_tcp_alloc (void);
+err_t sock_tcp_alloc (struct sock_tcp **sock_ptr);
 
 /*
  * Initialize a blank sock_tcp with a given already-existing fd
  */
-int sock_tcp_init_fd (struct sock_tcp *sock, int fd);
+err_t sock_tcp_init_fd (struct sock_tcp *sock, int fd);
 
 /*
  * Initialize a blank sock_tcp by connecting
  */
-int sock_tcp_init_connect (struct sock_tcp *sock, const char *hostname, const char *service);
+err_t sock_tcp_init_connect (struct sock_tcp *sock, const char *hostname, const char *service);
+
+/*
+ * Release a non-connected sock_tcp
+ */
+void sock_tcp_release (struct sock_tcp *sock);
 
 #endif /* SOCK_TCP_H */