src/lib/tcp_server.c
author Tero Marttila <terom@fixme.fi>
Thu, 28 May 2009 01:17:36 +0300
branchnew-lib-errors
changeset 219 cefec18b8268
parent 192 src/tcp_server.c@8cabaf67cc90
permissions -rw-r--r--
some of the lib/transport stuff compiles
#include "tcp_internal.h"
#include "sock_internal.h"
#include "log.h"

#include <unistd.h>

/*
 * Service methods
 */
void tcp_server__deinit (service_t *service)
{
    struct tcp_server *serv = service_check(service, &tcp_server_type);

    tcp_server_deinit(serv);
}

/*
 * Service type
 */
const struct service_type tcp_server_type = {
    .base_type = {
        .parent = &service_type_type,
    },
    .methods = {
        .deinit = tcp_server__deinit,
    },
};

/*
 * We got a new client, build a transport for it and give it to the user
 */
static err_t tcp_server_client (struct tcp_server *serv, evutil_socket_t sock, error_t *err)
{
    struct tcp_transport *trans;
    
    // create a new transport for it, this also makes it nonblocking
    if (tcp_transport_create(&trans, &serv->base_service.info.trans_info, sock, err))
        goto error;

    // make it connected
    // this will call transport_callbacks::on_connect, which is all the user needs
    if (tcp_transport_connected(trans, err))
        goto error;

    // ok
    return SUCCESS;

error:
    // cleanup
    if (trans)
        tcp_transport_destroy(trans);

    return ERROR_CODE(err);    
}

/*
 * Libevent callback
 */
static void tcp_server_on_accept (evutil_socket_t sock, short what, void *arg)
{
    struct tcp_server *serv = arg;
    evutil_socket_t client_sock;
    error_t err;

    (void) what;

    // accept as a new client connection
    if ((client_sock = accept(sock, NULL, NULL)) < 0 && errno != EAGAIN)
        JUMP_SET_ERROR_ERRNO(&err, ERR_ACCEPT);

    // spurious read event?
    if (client_sock < 0)
        return;

    // handle it
    if (tcp_server_client(serv, client_sock, &err))
        goto error;

    // ok
    return;

error:
    if (client_sock >= 0)
        EVUTIL_CLOSESOCKET(client_sock);

    // faaail
    service_error(&serv->base_service, &err);
}

/*
 * Attempts to construct a listen()'d socket with the given addr, and return it
 *
 * @param addr the addrinfo to try and create a socket for
 * @param err returned error info
 * @return listening socket, or -err_t on error
 */
static int tcp_server_sock_addr (struct addrinfo *addr, error_t *err)
{
    evutil_socket_t sock;

    // create the sock
    if ((sock = tcp_sock_create(addr, err)) < 0)
        goto error;

    // bind it
    if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0)
        JUMP_SET_ERROR_ERRNO(err, ERR_BIND);

    // listen
    if (listen(sock, TCP_SERVER_BACKLOG) < 0)
        JUMP_SET_ERROR_ERRNO(err, ERR_LISTEN);
    
    // ok, valid socket
    return sock;

error:
    if (sock >= 0)
        // cleanup
        EVUTIL_CLOSESOCKET(sock);
    
    return -ERROR_CODE(err);
}

/*
 * Construct a listen()'d socket with the given resolver result, and return it.
 *
 * @param rr the resolver lookup result to create a socket for
 * @param err returned error info
 * @return listening socket, or -err_t on error
 */
static int tcp_server_sock (struct resolve_result *rr, error_t *err)
{
    struct addrinfo *addr;
    evutil_socket_t sock;

    // try each addrinfo
    while ((addr = resolve_result_next(rr))) {
        // attempt to construct given socket
        if ((sock = tcp_server_sock_addr(addr, err)) < 0)
            // log an informative error warning
            log_warn_error(err, "%s", resolve_addr_text(addr)); 

        else
            // got a valid socket 
            break;
    }
    
    if (sock >= 0)
        // valid socket
        return sock;

    else
        // some error occured
        return -ERROR_CODE(err);
}

err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err)
{
    struct resolve_result rr;
    evutil_socket_t sock;

    // get the global event_base
    struct event_base *ev_base = _sock_stream_ctx.ev_base;

    // init the resolver
    resolve_result_init(&rr);

    // resolve the interface/service
    if (resolve_addr(&rr, interface, service, SOCK_STREAM, AI_PASSIVE, err))
        return ERROR_CODE(err);
    
    // create the socket
    if ((sock = tcp_server_sock(&rr, err)) < 0)
        goto error;

    // deinit lookup results
    resolve_result_deinit(&rr);

    // make it nonblocking
    if (evutil_make_socket_nonblocking(sock))
        JUMP_SET_ERROR_STR(err, ERR_MISC, "evutil_make_socket_nonblocking");
    
    // construct event for the sock
    if ((serv->ev = event_new(ev_base, sock, EV_READ | EV_PERSIST, tcp_server_on_accept, serv)) == NULL)
        JUMP_SET_ERROR(err, ERR_EVENT_NEW);

    // add it
    if (event_add(serv->ev, NULL))
        JUMP_SET_ERROR(err, ERR_EVENT_ADD);

    // ok
    return SUCCESS;

error:
    // deinit results just to be sure
    resolve_result_deinit(&rr);

    if (sock >= 0 && !serv->ev)
        // need to close socket ourselves, because we couldn't register our event for it
        EVUTIL_CLOSESOCKET(sock);
    
    // general cleanup
    tcp_server_deinit(serv);
    
    return ERROR_CODE(err);
}

void tcp_server_deinit (struct tcp_server *serv)
{
    if (serv->ev) {
        // ignore errors
        event_del(serv->ev);

        // ignore errors
        close(event_get_fd(serv->ev));
        
        // release event
        event_free(serv->ev);
        
        // invalidate
        serv->ev = NULL;
    }
}

static void tcp_server_destroy (struct tcp_server *serv)
{
    tcp_server_deinit(serv);

    free(serv);
}

/*
 * Public interface
 */
err_t tcp_listen (const struct service_info *info, service_t **service_ptr, 
        const char *interface, const char *service, error_t *err)
{
    struct tcp_server *serv;

    // alloc
    if ((serv = calloc(1, sizeof(*serv))) == NULL)
        return SET_ERROR(err, ERR_MEM);

    // init service
    service_init(&serv->base_service, &tcp_server_type, info);

    // init ourselves
    if (tcp_server_listen(serv, interface, service, err))
        goto error;

    // ok
    *service_ptr = &serv->base_service;

    return SUCCESS;

error:
    tcp_server_destroy(serv);

    return ERROR_CODE(err);
}