src/sock_tcp.c
author Tero Marttila <terom@fixme.fi>
Sun, 22 Feb 2009 10:16:28 +0200
changeset 10 9fe218576d13
parent 8 be88e543c8ff
child 11 14e79683c48c
permissions -rw-r--r--
fix sock_stream read/write return value, move line buffer inside of line_proto, add some initial code for event-based non-blocking operation

#include "sock_tcp.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.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);

    // bytes read
    *len = ret;

    return SUCCESS;
}

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

    // bytes read
    *len = ret;

    return SUCCESS;
}

static err_t sock_tcp_event_init (struct sock_stream *base_sock)
{
    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
    
    return SUCCESS;
}

static err_t sock_tcp_event_enable (struct sock_stream *base_sock, short mask)
{
    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
    
    return SUCCESS;
}

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

/*
 * Our basic socket event handler for driving our callbacks
 */
static void sock_tcp_event (evutil_socket_t fd, short what, void *arg) 
{
    struct sock_tcp *sock = arg;

    // invoke appropriate callback
    if (what & EV_READ && SOCK_TCP_BASE(sock)->cb_info->on_read)
        SOCK_TCP_BASE(sock)->cb_info->on_read(SOCK_TCP_BASE(sock), SOCK_TCP_BASE(sock)->cb_arg);

    if (what & EV_WRITE && SOCK_TCP_BASE(sock)->cb_info->on_write)
        SOCK_TCP_BASE(sock)->cb_info->on_read(SOCK_TCP_BASE(sock), SOCK_TCP_BASE(sock)->cb_arg);
}

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_ev (struct sock_tcp *sock, void (*ev_cb)(evutil_socket_t, short, void *), void *cb_arg)
{
    // require valid fd
    assert(sock->fd >= 0);
    
    // create new event
    if ((sock->ev = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_READ, ev_cb, cb_arg)) == NULL)
        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_NEW);

    // ok
    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_set_nonblock (struct sock_tcp *sock, int nonblock)
{
    // fcntl it
    // XXX: maintain old flags?
    if (fcntl(sock->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0)
        RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_FCNTL);

    // ok
    return SUCCESS;
}

void sock_tcp_release (struct sock_tcp *sock)
{
    // must not be connected
    assert(sock->fd < 0);

    // free
    free(sock);
}

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))) {
        // copy error_info
        SET_ERROR_INFO(err_info, sock_stream_error(SOCK_TCP_BASE(sock)));

        // cleanup
        sock_tcp_release(sock);
        
        // return error code
        return err;
    }

    // good
    *sock_ptr = SOCK_TCP_BASE(sock);

    return 0;
}