terom@12: #include terom@12: #include terom@12: #include terom@12: #include terom@12: #include terom@12: #include terom@12: #include terom@12: terom@23: #include terom@23: terom@23: #include terom@23: #include terom@23: #include terom@23: #include terom@23: terom@12: #include "common.h" terom@26: #include "socket.h" terom@12: #include "render.h" terom@23: #include "render_struct.h" terom@23: #include "render_net.h" terom@23: #include "render_thread.h" terom@23: #include "render_thread_struct.h" terom@12: terom@12: void sigpipe_ignore () { terom@12: struct sigaction sigpipe_action; terom@12: terom@12: memset(&sigpipe_action, 0, sizeof(sigpipe_action)); terom@12: sigpipe_action.sa_handler = SIG_IGN; terom@12: terom@12: if (sigaction(SIGPIPE, &sigpipe_action, NULL)) terom@12: perr_exit("sigaction"); terom@12: } terom@12: terom@24: void log_null (int severity, const char *msg) { terom@24: // ignore terom@24: } terom@24: terom@23: /* terom@23: * State needed to handle a client terom@23: */ terom@23: struct client_info { terom@23: // the client socket terom@23: evutil_socket_t socket; terom@12: terom@23: // the read-a-command buffer terom@23: struct bufferevent *bufev; terom@12: terom@23: // the write-a-mandelbrot stream terom@23: FILE *out_stream; terom@23: terom@23: // the render_thread op terom@23: // thread_info.is_active is useful terom@23: struct render_thread thread_info; terom@23: }; terom@23: terom@23: static void client_free (struct client_info *ctx) { terom@23: // free the read-a-command buffer terom@23: if (ctx->bufev) terom@23: bufferevent_free(ctx->bufev); terom@23: terom@23: // cancel the render thread if needed terom@23: if (ctx->thread_info.is_active) terom@23: render_thread_cancel(&ctx->thread_info); terom@23: terom@23: // deinit it in any case terom@23: render_thread_deinit(&ctx->thread_info); terom@23: terom@23: // close the write-a-mandelbrot stream, or just the socket terom@23: if (ctx->out_stream) { terom@23: if (fclose(ctx->out_stream)) terom@23: PWARNING("fclose"); terom@23: terom@23: } else if (ctx->socket != -1) { terom@23: if (close(ctx->socket)) terom@23: PWARNING("close"); terom@23: terom@12: } terom@12: terom@23: // free the client info terom@23: free(ctx); terom@12: } terom@12: terom@24: static void handle_render_done (struct render_thread *thread_info, int err, void *arg) { terom@23: struct client_info *ctx = arg; terom@12: terom@24: #if INFO_ENABLED terom@24: INFO("client [%p]: %s", ctx, err ? "failed" : "done"); terom@24: #endif terom@24: terom@23: // just free it, it takes care of closing it as well terom@23: client_free(ctx); terom@23: } terom@23: terom@23: static int handle_render_cmd (struct client_info *ctx, struct render_cmd *cmd) { terom@23: // the render ctx... terom@23: struct render render_info; terom@23: terom@23: // open it as a normal FILE* terom@23: if (!(ctx->out_stream = fdopen(ctx->socket, "w"))) terom@12: ERROR("fdopen"); terom@23: terom@23: #if INFO_ENABLED terom@23: 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); terom@23: #endif terom@23: terom@23: // set up the render_info terom@12: if ( terom@23: render_init(&render_info) terom@23: || render_set_mode(&render_info, cmd->mode) terom@23: || render_set_size(&render_info, cmd->img_w, cmd->img_h) terom@23: || render_region_raw(&render_info, cmd->x1, cmd->y1, cmd->x2, cmd->y2) terom@23: || render_io_stream(&render_info, ctx->out_stream) terom@12: ) terom@23: ERROR("render_*"); terom@23: terom@23: // start the render thread terom@23: if (render_thread_init(&ctx->thread_info, &render_info, &handle_render_done, ctx)) terom@23: ERROR("render_thread_init"); terom@12: terom@23: // ok, wait for it to complete terom@23: return 0; terom@12: terom@23: error: terom@23: // FAAAIL terom@23: return -1; terom@23: } terom@23: terom@23: static void handle_read (struct bufferevent *bev, void *arg) { terom@23: struct client_info *ctx = arg; terom@23: struct render_cmd cmd; terom@23: terom@23: // meh, just read it in terom@23: size_t len; terom@12: terom@23: // we set a watermark, so this should hold true terom@23: assert(len = bufferevent_read(bev, &cmd, sizeof(cmd)) == sizeof(cmd)); terom@19: terom@23: // fix the byte order terom@23: cmd.img_w = ntohl(cmd.img_w); terom@23: cmd.img_h = ntohl(cmd.img_h); terom@23: terom@23: // handle it terom@23: if (handle_render_cmd(ctx, &cmd)) terom@19: goto error; terom@19: terom@23: // ok terom@23: return; terom@23: terom@23: error: terom@23: client_free(ctx); terom@23: } terom@23: terom@23: static void handle_error (struct bufferevent *bev, short what, void *arg) { terom@23: struct client_info *ctx = arg; terom@19: terom@23: // read-EOF terom@23: if ((what & (EVBUFFER_READ | EVBUFFER_EOF)) && ctx->thread_info.is_active) { terom@23: // this is fine, expected, and doesn't matter terom@23: return; terom@23: } terom@23: terom@24: PWARNING("client [%p]: eventbuffer error: %s %s", ctx, terom@23: (what & EVBUFFER_READ) ? "read" : ((what & EVBUFFER_WRITE) ? "write" : "???"), terom@23: (what & EVBUFFER_EOF) ? "eof" : ((what & EVBUFFER_ERROR) ? "error" : ((what & EVBUFFER_TIMEOUT) ? "timeout" : "???")) terom@23: ); terom@12: terom@23: client_free(ctx); terom@23: } terom@23: terom@23: static void handle_accept (evutil_socket_t fd, short event, void *arg) { terom@23: struct client_info *ctx = NULL; terom@23: terom@23: evutil_socket_t socket = -1; terom@23: struct sockaddr_storage addr; terom@23: socklen_t addr_len; terom@19: terom@23: // arg is NULL and unused terom@23: (void) arg; terom@23: terom@23: // accept the connection terom@23: addr_len = sizeof(struct sockaddr_storage); terom@23: terom@23: if ((socket = accept(fd, (struct sockaddr *) &addr, &addr_len)) == -1) terom@23: PERROR("accept"); terom@23: terom@24: // alloc a new client_info terom@24: if (!(ctx = calloc(1, sizeof(*ctx)))) terom@24: ERROR("calloc"); terom@24: terom@24: // store the socket terom@24: ctx->socket = socket; terom@24: terom@23: #if INFO_ENABLED terom@23: assert(INET_ADDRSTRLEN < INET6_ADDRSTRLEN); terom@23: terom@23: char addr_buf[INET6_ADDRSTRLEN]; terom@23: const char *addr_str; terom@23: short nport; terom@23: terom@23: if (addr.ss_family == AF_UNIX) terom@23: addr_str = "local"; terom@23: else if (addr.ss_family == AF_INET || addr.ss_family == AF_INET6) { terom@23: const void *src; terom@23: terom@23: if (addr.ss_family == AF_INET) { terom@23: src = &(((struct sockaddr_in *) &addr)->sin_addr); terom@23: nport = ((struct sockaddr_in *) &addr)->sin_port; terom@23: } else { terom@23: src = &(((struct sockaddr_in6 *) &addr)->sin6_addr); terom@23: nport = ((struct sockaddr_in6 *) &addr)->sin6_port; terom@23: } terom@23: terom@23: if (!(inet_ntop(addr.ss_family, src, addr_buf, sizeof(addr_buf)))) terom@23: PERROR("inet_ntop"); terom@23: terom@23: addr_str = addr_buf; terom@23: } terom@23: terom@24: INFO("client [%p]: accept from %s:%hu", ctx, addr_str, ntohs(nport)); terom@23: #endif terom@23: terom@23: // then a bufferevent so that we can read in the command terom@23: if (!(ctx->bufev = bufferevent_new(ctx->socket, &handle_read, NULL, &handle_error, ctx))) terom@23: ERROR("bufferevent_new"); terom@23: terom@23: // and enable it for read only terom@23: if (bufferevent_enable(ctx->bufev, EV_READ)) terom@23: ERROR("bufferevent_enable"); terom@23: terom@23: // set the watermark for receiving the render_cmd terom@23: bufferevent_setwatermark(ctx->bufev, EV_READ, sizeof(struct render_cmd), 0); terom@23: terom@23: // now we just wait for the cmd... terom@24: return; terom@12: terom@12: error: terom@21: if (ctx) terom@23: client_free(ctx); terom@23: else if (socket != -1) terom@23: close(socket); terom@12: } terom@12: terom@12: int main (int argc, char** argv) { terom@23: struct event_base *ev_base; terom@26: struct config_endpoint endpoint; terom@23: int ssock; terom@26: terom@12: // parse arguments terom@12: int opt; terom@26: const char *listen_spec = NULL; terom@24: int enable_debug = 0; terom@12: terom@12: while ((opt = getopt(argc, argv, "l:")) != -1) { terom@12: switch (opt) { terom@12: case 'l': terom@26: if (listen_spec) terom@12: ERROR("only specify -l once"); terom@12: terom@26: listen_spec = optarg; terom@12: break; terom@12: terom@24: case 'd': terom@24: // enable libevent debugging terom@24: enable_debug = 1; terom@24: break; terom@24: terom@12: default: terom@26: err_exit("Usage: %s [-l addr_spec] [-d]", argv[0]); terom@12: } terom@12: } terom@12: terom@24: // init libevent terom@24: if (!(ev_base = event_init())) terom@24: FATAL("event_init"); terom@24: terom@24: // per default it is enabled terom@24: if (!enable_debug) terom@24: event_set_log_callback(&log_null); terom@12: terom@12: // create the socket terom@26: endpoint_init(&endpoint, RENDER_PORT); terom@12: terom@26: if (endpoint_parse(&endpoint, listen_spec)) terom@26: goto error; terom@12: terom@26: if ((ssock = socket_listen(&endpoint, SOCK_STREAM)) == -1) terom@26: goto error; terom@26: terom@23: // create the listen event terom@23: struct event listen_ev; terom@23: terom@24: event_set(&listen_ev, ssock, EV_READ | EV_PERSIST, &handle_accept, NULL); terom@23: terom@23: if (event_add(&listen_ev, NULL)) terom@23: PERROR("event_add"); terom@23: terom@12: // ignore sigpipe terom@12: sigpipe_ignore(); terom@12: terom@23: // run the libevent mainloop terom@26: INFO("run"); terom@23: terom@23: if (event_base_dispatch(ev_base)) terom@23: WARNING("event_dispatch"); terom@12: terom@24: INFO("SHUTDOWN"); terom@23: terom@23: event_base_free(ev_base); terom@23: terom@23: // succesful exit terom@23: return EXIT_SUCCESS; terom@12: terom@12: error: terom@23: // failure terom@23: return EXIT_FAILURE; terom@12: } terom@12: