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

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