source: libcaca/trunk/src/cacaserver.c @ 2821

Last change on this file since 2821 was 2821, checked in by Sam Hocevar, 11 years ago

Starting refactoring to get rid of libcucul. The initial reason for the
split is rendered moot by the plugin system: when enabled, binaries do
not link directly with libX11 or libGL. I hope this is a step towards
more consisteny and clarity.

  • Property svn:keywords set to Id
File size: 14.5 KB
RevLine 
[554]1/*
[703]2 *  cacaserver    Colour ASCII-Art library
3 *  Copyright (c) 2006 Jean-Yves Lamoureux <jylam@lnxscene.org>
4 *                2006 Sam Hocevar <sam@zoy.org>
[554]5 *                All Rights Reserved
6 *
[769]7 *  $Id: cacaserver.c 2821 2008-09-27 13:12:46Z sam $
8 *
[1462]9 *  This program is free software. It comes without any warranty, to
[1452]10 *  the extent permitted by applicable law. You can redistribute it
11 *  and/or modify it under the terms of the Do What The Fuck You Want
12 *  To Public License, Version 2, as published by Sam Hocevar. See
[554]13 *  http://sam.zoy.org/wtfpl/COPYING for more details.
14 */
15
16#include "config.h"
17
18#include <stdio.h>
[703]19#include <string.h>
[554]20#include <stdlib.h>
[865]21#if defined(HAVE_ARPA_INET_H)
22#   include <arpa/inet.h>
23#elif defined(HAVE_WINSOCK2_H)
24#   include <winsock2.h>
[866]25#   include <ws2tcpip.h>
[1770]26#   define USE_WINSOCK 1
[865]27#endif
[1913]28#if defined(HAVE_NETINET_IN_H)
29#   include <netinet/in.h>
30#endif
[1300]31#if defined(HAVE_UNISTD_H)
32#   include <unistd.h>
33#endif
[554]34#include <sys/types.h>
35#include <sys/socket.h>
[575]36#include <fcntl.h>
[590]37#include <signal.h>
38#include <errno.h>
[554]39#include <stdarg.h>
40
[1770]41#ifndef USE_WINSOCK
42#   define USE_WINSOCK 0
43#endif
44
[2821]45#include "caca.h"
[554]46
[601]47#define BACKLOG 1337     /* Number of pending connections */
48#define INBUFFER 32      /* Size of per-client input buffer */
49#define OUTBUFFER 300000 /* Size of per-client output buffer */
[554]50
[596]51/* Following vars are static */
52#define INIT_PREFIX \
[619]53    "\xff\xfb\x01"     /* WILL ECHO */ \
54    "\xff\xfb\x03"     /* WILL SUPPRESS GO AHEAD */ \
55    "\xff\xfd\x31"     /* DO NAWS */ \
56    "\xff\x1f\xfa____" /* SB NAWS */ \
57    "\xff\xf0"         /* SE */  \
[596]58    "\x1b]2;caca for the network\x07" /* Change window title */ \
[619]59    "\x1b[H\x1b[J" /* Clear screen */
60    /*"\x1b[?25l"*/ /* Hide cursor */
[596]61
62#define ANSI_PREFIX \
63    "\x1b[1;1H" /* move(0,0) */ \
64    "\x1b[1;1H" /* move(0,0) again */
65
66#define ANSI_RESET \
67    "    " /* Garbage */ \
68    "\x1b[?1049h" /* Clear screen */ \
69    "\x1b[?1049h" /* Clear screen again */
70
[619]71static char const telnet_commands[16][5] =
[703]72{
73    "SE  ", "NOP ", "DM  ", "BRK ", "IP  ", "AO  ", "AYT ", "EC  ",
74    "EL  ", "GA  ", "SB  ", "WILL", "WONT", "DO  ", "DONT", "IAC "
75};
[619]76
77static char const telnet_options[37][5] =
[703]78{
79    "????", "ECHO", "????", "SUGH", "????", "STTS", "TIMK", "????",
80    "????", "????", "????", "????", "????", "????", "????", "????",
81    "????", "????", "????", "????", "????", "????", "????", "????",
82    "TTYP", "????", "????", "????", "????", "????", "????", "NAWS",
83    "TRSP", "RMFC", "LIMO", "????", "EVAR"
84};
[619]85
86#define COMMAND_NAME(x) (x>=240)?telnet_commands[x-240]:"????"
87#define OPTION_NAME(x) (x<=36)?telnet_options[x]:"????"
88
[596]89struct client
90{
91    int fd;
92    int ready;
[601]93    uint8_t inbuf[INBUFFER];
94    int inbytes;
95    uint8_t outbuf[OUTBUFFER];
[596]96    int start, stop;
97};
98
[703]99struct server
[590]100{
[554]101    unsigned int width, height;
102    unsigned int port;
[584]103    int sockfd;
[555]104    struct sockaddr_in my_addr;
[554]105    socklen_t sin_size;
[575]106
[703]107    /* Input buffer */
[707]108    uint8_t *input;
109    unsigned int read;
[703]110
[596]111    char prefix[sizeof(INIT_PREFIX)];
112
[2821]113    caca_canvas_t *canvas;
[1308]114    void *buffer;
[2313]115    size_t buflen;
[584]116
[575]117    int client_count;
[596]118    struct client *clients;
119
[611]120    RETSIGTYPE (*sigpipe_handler)(int);
[554]121};
122
[703]123static void manage_connections(struct server *server);
124static int send_data(struct server *server, struct client *c);
[601]125ssize_t nonblock_write(int fd, void *buf, size_t len);
[554]126
[703]127int main(void)
[554]128{
[703]129    int i, yes = 1, flags;
130    struct server *server;
[611]131    char *tmp;
[575]132
[1770]133#if USE_WINSOCK
134    WORD winsockVersion;
135    WSADATA wsaData;
136    winsockVersion = MAKEWORD(1, 1);
[717]137
[1770]138    WSAStartup(winsockVersion, &wsaData);
[717]139#endif
[703]140    server = malloc(sizeof(struct server));
[554]141
[1283]142    server->input = malloc(12);
[707]143    server->read = 0;
144
[703]145    server->client_count = 0;
146    server->clients = NULL;
147    server->port = 0xCACA; /* 51914 */
[611]148
[596]149    /* FIXME, handle >255 sizes */
[703]150    memcpy(server->prefix, INIT_PREFIX, sizeof(INIT_PREFIX));
151    tmp = strstr(server->prefix, "____");
[2300]152    tmp[0] = (uint8_t) (server->width & 0xff00) >> 8;
153    tmp[1] = (uint8_t) server->width & 0xff;
154    tmp[2] = (uint8_t) (server->height & 0xff00) >> 8;
155    tmp[3] = (uint8_t) server->height & 0xff;
[554]156
[703]157    if((server->sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
[596]158    {
[554]159        perror("socket");
160        return -1;
161    }
[585]162
[703]163    if(setsockopt(server->sockfd, SOL_SOCKET,
164                  SO_REUSEADDR, &yes, sizeof(int)) == -1)
[596]165    {
[590]166        perror("setsockopt SO_REUSEADDR");
[554]167        return -1;
168    }
169
[703]170    server->my_addr.sin_family = AF_INET;
171    server-> my_addr.sin_port = htons(server->port);
172    server->my_addr.sin_addr.s_addr = INADDR_ANY;
173    memset(&(server->my_addr.sin_zero), '\0', 8);
[554]174
[703]175    if(bind(server->sockfd, (struct sockaddr *)&server->my_addr,
[596]176             sizeof(struct sockaddr)) == -1)
177    {
[554]178        perror("bind");
179        return -1;
180    }
[575]181
182    /* Non blocking socket */
[703]183    flags = fcntl(server->sockfd, F_GETFL, 0);
184    fcntl(server->sockfd, F_SETFL, flags | O_NONBLOCK);
[575]185
[703]186    if(listen(server->sockfd, BACKLOG) == -1)
[596]187    {
[554]188        perror("listen");
189        return -1;
190    }
191
[2821]192    server->canvas = caca_create_canvas(0, 0);
[777]193    server->buffer = NULL;
[584]194
[590]195    /* Ignore SIGPIPE */
[703]196    server->sigpipe_handler = signal(SIGPIPE, SIG_IGN);
[590]197
[601]198    fprintf(stderr, "initialised network, listening on port %i\n",
[703]199                    server->port);
[601]200
[703]201    /* Main loop */
202    for(;;)
[596]203    {
[1315]204restart:
[703]205        /* Manage new connections as this function will be called sometimes
206         * more often than display */
207        manage_connections(server);
[584]208
[703]209        /* Read data from stdin */
[1315]210        if(server->read < 12)
211        {
212            read(0, server->input + server->read, 12 - server->read);
213            server->read = 12;
214        }
[609]215
[2821]216        while(caca_import_memory(server->canvas, server->input,
[1315]217                                  server->read, "caca") < 0)
[707]218        {
[1315]219            memmove(server->input, server->input + 1, server->read - 1);
220            read(0, server->input + server->read - 1, 1);
[707]221        }
222
[1315]223        for(;;)
224        {
[2313]225            ssize_t needed, wanted;
[707]226
[2821]227            needed = caca_import_memory(server->canvas, server->input,
[1315]228                                         server->read, "caca");
229            if(needed < 0)
230                goto restart;
[707]231
[1315]232            if(needed > 0)
233            {
234                server->read -= needed;
235                memmove(server->input, server->input + needed, server->read);
236                break;
237            }
[721]238
[1315]239            server->input = realloc(server->input, server->read + 128);
240            wanted = read(0, server->input + server->read, 128);
241            if(wanted < 0)
242                goto restart;
243            server->read += wanted;
244        }
[2313]245
[703]246        /* Free the previous export buffer, if any */
[777]247        if(server->buffer)
[703]248        {
[1308]249            free(server->buffer);
[777]250            server->buffer = NULL;
[703]251        }
[596]252
[703]253        /* Get ANSI representation of the image and skip the end-of buffer
[1283]254         * linefeed ("\r\n", 2 byte) */
[2821]255        server->buffer = caca_export_memory(server->canvas, "utf8cr",
[1308]256                                             &server->buflen);
[818]257        server->buflen -= 2;
[596]258
[703]259        for(i = 0; i < server->client_count; i++)
260        {
261            if(server->clients[i].fd == -1)
262                continue;
[554]263
[703]264            if(send_data(server, &server->clients[i]))
265            {
[950]266                fprintf(stderr, "[%i] dropped connection\n",
[703]267                                server->clients[i].fd);
268                close(server->clients[i].fd);
269                server->clients[i].fd = -1;
270            }
271        }
[609]272    }
273
[703]274    /* Kill all remaining clients */
275    for(i = 0; i < server->client_count; i++)
[584]276    {
[703]277        if(server->clients[i].fd == -1)
[590]278            continue;
279
[703]280        close(server->clients[i].fd);
281        server->clients[i].fd = -1;
[584]282    }
[575]283
[777]284    if(server->buffer)
[1308]285        free(server->buffer);
[590]286
[2821]287    caca_free_canvas(server->canvas);
[1308]288
[703]289    /* Restore SIGPIPE handler */
290    signal(SIGPIPE, server->sigpipe_handler);
[554]291
[703]292    free(server);
[584]293
[1770]294#if USE_WINSOCK
[717]295    WSACleanup();
296#endif
[554]297    return 0;
298}
299
[584]300/*
301 * XXX: The following functions are local
302 */
[554]303
[703]304static void manage_connections(struct server *server)
[584]305{
[596]306    int fd, flags;
307    struct sockaddr_in remote_addr;
308    socklen_t len = sizeof(struct sockaddr_in);
[584]309
[703]310    fd = accept(server->sockfd, (struct sockaddr *)&remote_addr, &len);
[590]311    if(fd == -1)
312        return;
313
[950]314    fprintf(stderr, "[%i] connected from %s\n",
[601]315                    fd, inet_ntoa(remote_addr.sin_addr));
316
[590]317    /* Non blocking socket */
[596]318    flags = fcntl(fd, F_SETFL, 0);
319    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
[590]320
[703]321    if(server->clients == NULL)
[584]322    {
[703]323        server->clients = malloc(sizeof(struct client));
324        if(server->clients == NULL)
[590]325            return;
326    }
327    else
328    {
[703]329        server->clients = realloc(server->clients,
330                            (server->client_count+1) * sizeof(struct client));
[590]331    }
[584]332
[703]333    server->clients[server->client_count].fd = fd;
334    server->clients[server->client_count].ready = 0;
335    server->clients[server->client_count].inbytes = 0;
336    server->clients[server->client_count].start = 0;
337    server->clients[server->client_count].stop = 0;
[596]338
339    /* If we already have data to send, send it to the new client */
[703]340    if(send_data(server, &server->clients[server->client_count]))
[601]341    {
[950]342        fprintf(stderr, "[%i] dropped connection\n", fd);
[601]343        close(fd);
[703]344        server->clients[server->client_count].fd = -1;
[601]345        return;
346    }
[596]347
[703]348    server->client_count++;
[584]349}
350
[703]351static int send_data(struct server *server, struct client *c)
[584]352{
[590]353    ssize_t ret;
354
[596]355    /* Not for us */
356    if(c->fd < 0)
357        return -1;
358
[601]359    /* Listen to incoming data */
[596]360    for(;;)
361    {
[601]362        ret = read(c->fd, c->inbuf + c->inbytes, 1);
[596]363        if(ret <= 0)
364            break;
365
[601]366        c->inbytes++;
367
368        /* Check for telnet sequences */
369        if(c->inbuf[0] == 0xff)
370        {
371            if(c->inbytes == 1)
372            {
373                ;
374            }
375            else if(c->inbuf[1] == 0xfd || c->inbuf[1] == 0xfc)
376            {
377                if(c->inbytes == 3)
378                {
[950]379                    fprintf(stderr, "[%i] said: %.02x %.02x %.02x (%s %s %s)\n",
[619]380                            c->fd, c->inbuf[0], c->inbuf[1], c->inbuf[2],
[644]381                            COMMAND_NAME(c->inbuf[0]), COMMAND_NAME(c->inbuf[1]), OPTION_NAME(c->inbuf[2]));
[601]382                    /* Just ignore, lol */
383                    c->inbytes = 0;
384                }
385            }
386            else
387                c->inbytes = 0;
388        }
389        else if(c->inbytes == 1)
390        {
391            if(c->inbuf[0] == 0x03)
392            {
[950]393                fprintf(stderr, "[%i] pressed C-c\n", c->fd);
[601]394                return -1; /* User requested to quit */
395            }
396
397            c->inbytes = 0;
398        }
[596]399    }
400
401    /* Send the telnet initialisation commands */
402    if(!c->ready)
403    {
[703]404        ret = nonblock_write(c->fd, server->prefix, sizeof(INIT_PREFIX));
[596]405        if(ret == -1)
406            return (errno == EAGAIN) ? 0 : -1;
407
408        if(ret < (ssize_t)sizeof(INIT_PREFIX))
409            return 0;
410
411        c->ready = 1;
412    }
413
[584]414    /* No error, there's just nothing to send yet */
[777]415    if(!server->buffer)
[584]416        return 0;
417
[596]418    /* If we have backlog, send the backlog */
419    if(c->stop)
420    {
[601]421        ret = nonblock_write(c->fd, c->outbuf + c->start, c->stop - c->start);
[584]422
[596]423        if(ret == -1)
424        {
425            if(errno == EAGAIN)
426                ret = 0;
427            else
[818]428            {
[950]429                fprintf(stderr, "[%i] failed (%s)\n",
[818]430                        c->fd, strerror(errno));
[596]431                return -1;
[818]432            }
[596]433        }
[590]434
[596]435        if(ret == c->stop - c->start)
436        {
437            /* We got rid of the backlog! */
438            c->start = c->stop = 0;
439        }
440        else
441        {
442            c->start += ret;
[590]443
[777]444            if(c->stop - c->start + strlen(ANSI_PREFIX) + server->buflen
[601]445                > OUTBUFFER)
[596]446            {
447                /* Overflow! Empty buffer and start again */
[601]448                memcpy(c->outbuf, ANSI_RESET, strlen(ANSI_RESET));
[596]449                c->start = 0;
450                c->stop = strlen(ANSI_RESET);
451                return 0;
452            }
453
454            /* Need to move? */
[777]455            if(c->stop + strlen(ANSI_PREFIX) + server->buflen > OUTBUFFER)
[596]456            {
[601]457                memmove(c->outbuf, c->outbuf + c->start, c->stop - c->start);
[596]458                c->stop -= c->start;
459                c->start = 0;
460            }
461
[601]462            memcpy(c->outbuf + c->stop, ANSI_PREFIX, strlen(ANSI_PREFIX));
[596]463            c->stop += strlen(ANSI_PREFIX);
[1308]464            memcpy(c->outbuf + c->stop, server->buffer, server->buflen);
[777]465            c->stop += server->buflen;
[596]466
467            return 0;
468        }
469    }
470
471    /* We no longer have backlog, send our new data */
472
473    /* Send ANSI prefix */
474    ret = nonblock_write(c->fd, ANSI_PREFIX, strlen(ANSI_PREFIX));
[590]475    if(ret == -1)
[596]476    {
477        if(errno == EAGAIN)
478            ret = 0;
479        else
[818]480        {
[950]481            fprintf(stderr, "[%i] failed (%s)\n", c->fd, strerror(errno));
[596]482            return -1;
[818]483        }
[596]484    }
[590]485
[596]486    if(ret < (ssize_t)strlen(ANSI_PREFIX))
487    {
[777]488        if(strlen(ANSI_PREFIX) + server->buflen > OUTBUFFER)
[596]489        {
490            /* Overflow! Empty buffer and start again */
[601]491            memcpy(c->outbuf, ANSI_RESET, strlen(ANSI_RESET));
[596]492            c->start = 0;
493            c->stop = strlen(ANSI_RESET);
494            return 0;
495        }
496
[601]497        memcpy(c->outbuf, ANSI_PREFIX, strlen(ANSI_PREFIX) - ret);
[596]498        c->stop = strlen(ANSI_PREFIX) - ret;
[1308]499        memcpy(c->outbuf + c->stop, server->buffer, server->buflen);
[777]500        c->stop += server->buflen;
[596]501
502        return 0;
503    }
504
[585]505    /* Send actual data */
[1308]506    ret = nonblock_write(c->fd, server->buffer, server->buflen);
[590]507    if(ret == -1)
[596]508    {
509        if(errno == EAGAIN)
510            ret = 0;
511        else
[818]512        {
[950]513            fprintf(stderr, "[%i] failed (%s)\n", c->fd, strerror(errno));
[596]514            return -1;
[818]515        }
[596]516    }
[584]517
[777]518    if(ret < (int)server->buflen)
[596]519    {
[777]520        if(server->buflen > OUTBUFFER)
[596]521        {
522            /* Overflow! Empty buffer and start again */
[601]523            memcpy(c->outbuf, ANSI_RESET, strlen(ANSI_RESET));
[596]524            c->start = 0;
525            c->stop = strlen(ANSI_RESET);
526            return 0;
527        }
528
[1308]529        memcpy(c->outbuf, server->buffer, server->buflen - ret);
[777]530        c->stop = server->buflen - ret;
[596]531
532        return 0;
533    }
534
[584]535    return 0;
536}
537
[601]538ssize_t nonblock_write(int fd, void *buf, size_t len)
[590]539{
540    size_t total = 0;
541    ssize_t ret;
542
543    while(total < len)
544    {
545        do
546        {
547            ret = write(fd, buf, len);
548        }
[596]549        while(ret < 0 && errno == EINTR);
[590]550
551        if(ret < 0)
552            return ret;
553        else if(ret == 0)
554            return total;
555
556        total += len;
[601]557        buf = (void *)((uintptr_t)buf + len);
[590]558    }
559
560    return total;
561}
562
Note: See TracBrowser for help on using the repository browser.