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