node_main.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 Aug 2008 21:30:32 +0300
changeset 41 540737bf6bac
parent 26 6d615203d963
permissions -rw-r--r--
sending requests, and partial support for receiving -- incomplete, not tested
#include <stdio.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#include <assert.h>

#include <event2/event.h>
#include <event2/event_struct.h>
#include <event2/event_compat.h>
#include <event2/bufferevent.h>

#include "common.h"
#include "socket.h"
#include "render.h"
#include "render_struct.h"
#include "render_net.h"
#include "render_thread.h"
#include "render_thread_struct.h"

void sigpipe_ignore () {
    struct sigaction sigpipe_action;

    memset(&sigpipe_action, 0, sizeof(sigpipe_action));
    sigpipe_action.sa_handler = SIG_IGN;

    if (sigaction(SIGPIPE, &sigpipe_action, NULL))
        perr_exit("sigaction");
}

void log_null (int severity, const char *msg) {
    // ignore
}

/*
 * State needed to handle a client
 */
struct client_info {
    // the client socket
    evutil_socket_t socket;
    
    // the read-a-command buffer
    struct bufferevent *bufev;

    // the write-a-mandelbrot stream
    FILE *out_stream;

    // the render_thread op
    // thread_info.is_active is useful
    struct render_thread thread_info;
};

static void client_free (struct client_info *ctx) {
    // free the read-a-command buffer
    if (ctx->bufev)
        bufferevent_free(ctx->bufev);
    
    // cancel the render thread if needed
    if (ctx->thread_info.is_active)
        render_thread_cancel(&ctx->thread_info);
    
    // deinit it in any case
    render_thread_deinit(&ctx->thread_info);

    // close the write-a-mandelbrot stream, or just the socket
    if (ctx->out_stream) {
        if (fclose(ctx->out_stream))
            PWARNING("fclose");

    } else if (ctx->socket != -1) {
        if (close(ctx->socket))
            PWARNING("close");

    }

    // free the client info
    free(ctx);
}

static void handle_render_done (struct render_thread *thread_info, int err, void *arg) {
    struct client_info *ctx = arg;

#if INFO_ENABLED
    INFO("client [%p]: %s", ctx, err ? "failed" : "done");
#endif    

    // just free it, it takes care of closing it as well
    client_free(ctx);
}

static int handle_render_cmd (struct client_info *ctx, struct render_cmd *cmd) {
    // the render ctx...
    struct render render_info;

    // open it as a normal FILE*
    if (!(ctx->out_stream = fdopen(ctx->socket, "w")))
        ERROR("fdopen");

#if INFO_ENABLED
    INFO("client [%p]: render [%ux%u] (%f, %f) -> (%f, %f)", ctx, cmd->img_w, cmd->img_h, cmd->x1, cmd->y1, cmd->x2, cmd->y2);
#endif    

    // set up the render_info
    if (
            render_init(&render_info)
         || render_set_mode(&render_info, cmd->mode)
         || render_set_size(&render_info, cmd->img_w, cmd->img_h)
         || render_region_raw(&render_info, cmd->x1, cmd->y1, cmd->x2, cmd->y2)
         || render_io_stream(&render_info, ctx->out_stream)
    )
        ERROR("render_*");
    
    // start the render thread
    if (render_thread_init(&ctx->thread_info, &render_info, &handle_render_done, ctx))
        ERROR("render_thread_init");

    // ok, wait for it to complete
    return 0;

error:
    // FAAAIL
    return -1;
}

static void handle_read (struct bufferevent *bev, void *arg) {
    struct client_info *ctx = arg;
    struct render_cmd cmd;

    // meh, just read it in
    size_t len;
    
    // we set a watermark, so this should hold true
    assert(len = bufferevent_read(bev, &cmd, sizeof(cmd)) == sizeof(cmd));

    // fix the byte order
    cmd.img_w = ntohl(cmd.img_w);
    cmd.img_h = ntohl(cmd.img_h);

    // handle it
    if (handle_render_cmd(ctx, &cmd))
        goto error;
    
    // ok
    return;

error:
    client_free(ctx);
}

static void handle_error (struct bufferevent *bev, short what, void *arg) {
    struct client_info *ctx = arg;
    
    // read-EOF
    if ((what & (EVBUFFER_READ | EVBUFFER_EOF)) && ctx->thread_info.is_active) {
        // this is fine, expected, and doesn't matter
        return;
    }
    
    PWARNING("client [%p]: eventbuffer error: %s %s", ctx,
        (what & EVBUFFER_READ) ? "read" : ((what & EVBUFFER_WRITE) ? "write" : "???"),
        (what & EVBUFFER_EOF) ? "eof" : ((what & EVBUFFER_ERROR) ? "error" : ((what & EVBUFFER_TIMEOUT) ? "timeout" : "???"))
    );

    client_free(ctx);
}

static void handle_accept (evutil_socket_t fd, short event, void *arg) {
    struct client_info *ctx = NULL;

    evutil_socket_t socket = -1;
    struct sockaddr_storage addr;
    socklen_t addr_len;
    
    // arg is NULL and unused
    (void) arg;
    
    // accept the connection
    addr_len = sizeof(struct sockaddr_storage);

    if ((socket = accept(fd, (struct sockaddr *) &addr, &addr_len)) == -1)
        PERROR("accept");
    
    // alloc a new client_info
    if (!(ctx = calloc(1, sizeof(*ctx))))
        ERROR("calloc");
    
    // store the socket
    ctx->socket = socket;

#if INFO_ENABLED
    assert(INET_ADDRSTRLEN < INET6_ADDRSTRLEN);

    char addr_buf[INET6_ADDRSTRLEN];
    const char *addr_str;
    short nport;

    if (addr.ss_family == AF_UNIX)
        addr_str = "local";
    else if (addr.ss_family == AF_INET || addr.ss_family == AF_INET6) {
        const void *src;
        
        if (addr.ss_family == AF_INET) {
            src = &(((struct sockaddr_in *) &addr)->sin_addr);
            nport = ((struct sockaddr_in *) &addr)->sin_port;
        } else {
            src = &(((struct sockaddr_in6 *) &addr)->sin6_addr);
            nport = ((struct sockaddr_in6 *) &addr)->sin6_port;
        }

        if (!(inet_ntop(addr.ss_family, src, addr_buf, sizeof(addr_buf))))
            PERROR("inet_ntop");

        addr_str = addr_buf;
    }

    INFO("client [%p]: accept from %s:%hu", ctx, addr_str, ntohs(nport));
#endif

    // then a bufferevent so that we can read in the command
    if (!(ctx->bufev = bufferevent_new(ctx->socket, &handle_read, NULL, &handle_error, ctx)))
        ERROR("bufferevent_new");
    
    // and enable it for read only
    if (bufferevent_enable(ctx->bufev, EV_READ))
        ERROR("bufferevent_enable");
    
    // set the watermark for receiving the render_cmd
    bufferevent_setwatermark(ctx->bufev, EV_READ, sizeof(struct render_cmd), 0);
    
    // now we just wait for the cmd...
    return;

error:
    if (ctx)
        client_free(ctx);
    else if (socket != -1)
        close(socket);
}

int main (int argc, char** argv) {
    struct event_base *ev_base;
    struct config_endpoint endpoint;
    int ssock;

    // parse arguments
    int opt;
    const char *listen_spec = NULL;
    int enable_debug = 0;

    while ((opt = getopt(argc, argv, "l:")) != -1) {
        switch (opt) {
            case 'l':
                if (listen_spec)
                    ERROR("only specify -l once");

                 listen_spec = optarg;
                break;

            case 'd':
                // enable libevent debugging
                enable_debug = 1;
                break;

            default:
                err_exit("Usage: %s [-l addr_spec] [-d]", argv[0]);
        }
    }
    
    // init libevent
    if (!(ev_base = event_init()))
        FATAL("event_init");

    // per default it is enabled
    if (!enable_debug)
        event_set_log_callback(&log_null);

    // create the socket
    endpoint_init(&endpoint, RENDER_PORT);

    if (endpoint_parse(&endpoint, listen_spec))
        goto error;

    if ((ssock = socket_listen(&endpoint, SOCK_STREAM)) == -1)
        goto error;

    // create the listen event
    struct event listen_ev;

    event_set(&listen_ev, ssock, EV_READ | EV_PERSIST, &handle_accept, NULL);

    if (event_add(&listen_ev, NULL))
        PERROR("event_add");
    
    // ignore sigpipe
    sigpipe_ignore();
    
    // run the libevent mainloop
    INFO("run");
 
    if (event_base_dispatch(ev_base))
        WARNING("event_dispatch");

    INFO("SHUTDOWN");
    
    event_base_free(ev_base);

    // succesful exit
    return EXIT_SUCCESS;

error:
    // failure
    return EXIT_FAILURE;
}