src/sock_tcp.c
author Tero Marttila <terom@fixme.fi>
Sun, 22 Feb 2009 06:52:55 +0200
changeset 4 a3ca0f97a075
parent 3 cc94ae754e2a
child 8 be88e543c8ff
permissions -rw-r--r--
change ERROR_* to use pointers again, and implement error_info for sock_init

#include "sock_tcp.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

/*
 * Our sock_stream_methods.read method
 */
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);

    else 
        // bytes read
        return ret;
}

/*
 * Our sock_stream_methods.write method
 */
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);

    else
        // bytes read
        return ret;
}

/*
 * Our sock_stream_type
 */
struct sock_stream_type sock_tcp_type = {
    .methods.read   = &sock_tcp_read,
    .methods.write  = &sock_tcp_write,
};

err_t sock_tcp_alloc (struct sock_tcp **sock_ptr)
{
    // alloc
    if ((*sock_ptr = calloc(1, sizeof(**sock_ptr))) == NULL)
        return ERR_CALLOC;
    
    // initialize base with sock_tcp_type
    sock_stream_init(SOCK_TCP_BASE(*sock_ptr), &sock_tcp_type);

    // done
    return SUCCESS;
}

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 SUCCESS;
}

err_t sock_tcp_init_connect (struct sock_tcp *sock, const char *hostname, const char *service)
{
    struct addrinfo hints, *res, *r;
    int err;
    RESET_ERROR(SOCK_TCP_ERR(sock));
    
    // hints
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // resolve
    if ((err = getaddrinfo(hostname, service, &hints, &res)))
        RETURN_SET_ERROR_EXTRA(SOCK_TCP_ERR(sock), ERR_GETADDRINFO, err);

    // try each result in turn
    for (r = res; r; r = r->ai_next) {
        // 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);

            // 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, 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;    
}

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
    if ((err = sock_tcp_alloc(&sock)))
        return err;

    // connect
    if ((err = sock_tcp_init_connect(sock, host, service))) {
        // set *err_info
        sock_stream_error(SOCK_TCP_BASE(sock), err_info);

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