source: ipc.c @ 5d1b3a95

Last change on this file since 5d1b3a95 was e046390, checked in by Wilmer van der Gaast <wilmer@…>, at 2009-10-10T23:25:54Z

Make purple use BitlBee's event handling API. Since the APIs never really
diverged too much this is fairly transparent. I did rename and redefine
GAIM_INPUT_* variables to really make it work without adding another stupid
layer in between.

One problem left, the new libpurple input API doesn't care about return
values. Fixing that in the next CL.

  • Property mode set to 100644
File size: 14.4 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2006 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* IPC - communication between BitlBee processes                        */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "ipc.h"
29#include "commands.h"
30#ifndef _WIN32
31#include <sys/un.h>
32#endif
33
34GSList *child_list = NULL;
35
36static void ipc_master_cmd_client( irc_t *data, char **cmd )
37{
38        /* Normally data points at an irc_t block, but for the IPC master
39           this is different. We think this scary cast is better than
40           creating a new command_t structure, just to make the compiler
41           happy. */
42        struct bitlbee_child *child = (void*) data;
43       
44        if( child && cmd[1] )
45        {
46                child->host = g_strdup( cmd[1] );
47                child->nick = g_strdup( cmd[2] );
48                child->realname = g_strdup( cmd[3] );
49        }
50       
51        if( g_strcasecmp( cmd[0], "CLIENT" ) == 0 )
52                ipc_to_children_str( "OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n",
53                                     (int) ( child ? child->pid : -1 ), cmd[2], cmd[1], cmd[3] );
54}
55
56static void ipc_master_cmd_die( irc_t *data, char **cmd )
57{
58        if( global.conf->runmode == RUNMODE_FORKDAEMON )
59                ipc_to_children_str( "DIE\r\n" );
60       
61        bitlbee_shutdown( NULL, -1, 0 );
62}
63
64static void ipc_master_cmd_deaf( irc_t *data, char **cmd )
65{
66        if( global.conf->runmode == RUNMODE_DAEMON )
67        {
68                b_event_remove( global.listen_watch_source_id );
69                close( global.listen_socket );
70               
71                global.listen_socket = global.listen_watch_source_id = -1;
72       
73                ipc_to_children_str( "OPERMSG :Closed listening socket, waiting "
74                                     "for all users to disconnect." );
75        }
76        else
77        {
78                ipc_to_children_str( "OPERMSG :The DEAF command only works in "
79                                     "normal daemon mode. Try DIE instead." );
80        }
81}
82
83void ipc_master_cmd_rehash( irc_t *data, char **cmd )
84{
85        runmode_t oldmode;
86       
87        oldmode = global.conf->runmode;
88       
89        g_free( global.conf );
90        global.conf = conf_load( 0, NULL );
91       
92        if( global.conf->runmode != oldmode )
93        {
94                log_message( LOGLVL_WARNING, "Can't change RunMode setting at runtime, restoring original setting" );
95                global.conf->runmode = oldmode;
96        }
97       
98        if( global.conf->runmode == RUNMODE_FORKDAEMON )
99                ipc_to_children( cmd );
100}
101
102void ipc_master_cmd_restart( irc_t *data, char **cmd )
103{
104        if( global.conf->runmode != RUNMODE_FORKDAEMON )
105        {
106                /* Tell child that this is unsupported. */
107                return;
108        }
109       
110        global.restart = -1;
111        bitlbee_shutdown( NULL, -1, 0 );
112}
113
114static const command_t ipc_master_commands[] = {
115        { "client",     3, ipc_master_cmd_client,     0 },
116        { "hello",      0, ipc_master_cmd_client,     0 },
117        { "die",        0, ipc_master_cmd_die,        0 },
118        { "deaf",       0, ipc_master_cmd_deaf,       0 },
119        { "wallops",    1, NULL,                      IPC_CMD_TO_CHILDREN },
120        { "wall",       1, NULL,                      IPC_CMD_TO_CHILDREN },
121        { "opermsg",    1, NULL,                      IPC_CMD_TO_CHILDREN },
122        { "rehash",     0, ipc_master_cmd_rehash,     0 },
123        { "kill",       2, NULL,                      IPC_CMD_TO_CHILDREN },
124        { "restart",    0, ipc_master_cmd_restart,    0 },
125        { NULL }
126};
127
128
129static void ipc_child_cmd_die( irc_t *irc, char **cmd )
130{
131        irc_abort( irc, 0, "Shutdown requested by operator" );
132}
133
134static void ipc_child_cmd_wallops( irc_t *irc, char **cmd )
135{
136        if( !( irc->status & USTATUS_LOGGED_IN ) )
137                return;
138       
139        if( strchr( irc->umode, 'w' ) )
140                irc_write( irc, ":%s WALLOPS :%s", irc->myhost, cmd[1] );
141}
142
143static void ipc_child_cmd_wall( irc_t *irc, char **cmd )
144{
145        if( !( irc->status & USTATUS_LOGGED_IN ) )
146                return;
147       
148        if( strchr( irc->umode, 's' ) )
149                irc_write( irc, ":%s NOTICE %s :%s", irc->myhost, irc->nick, cmd[1] );
150}
151
152static void ipc_child_cmd_opermsg( irc_t *irc, char **cmd )
153{
154        if( !( irc->status & USTATUS_LOGGED_IN ) )
155                return;
156       
157        if( strchr( irc->umode, 'o' ) )
158                irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->myhost, irc->nick, cmd[1] );
159}
160
161static void ipc_child_cmd_rehash( irc_t *irc, char **cmd )
162{
163        runmode_t oldmode;
164       
165        oldmode = global.conf->runmode;
166       
167        g_free( global.conf );
168        global.conf = conf_load( 0, NULL );
169       
170        global.conf->runmode = oldmode;
171}
172
173static void ipc_child_cmd_kill( irc_t *irc, char **cmd )
174{
175        if( !( irc->status & USTATUS_LOGGED_IN ) )
176                return;
177       
178        if( nick_cmp( cmd[1], irc->nick ) != 0 )
179                return;         /* It's not for us. */
180       
181        irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->mynick, irc->mynick, irc->myhost, irc->nick, cmd[2] );
182        irc_abort( irc, 0, "Killed by operator: %s", cmd[2] );
183}
184
185static void ipc_child_cmd_hello( irc_t *irc, char **cmd )
186{
187        if( !( irc->status & USTATUS_LOGGED_IN ) )
188                ipc_to_master_str( "HELLO\r\n" );
189        else
190                ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
191}
192
193static const command_t ipc_child_commands[] = {
194        { "die",        0, ipc_child_cmd_die,         0 },
195        { "wallops",    1, ipc_child_cmd_wallops,     0 },
196        { "wall",       1, ipc_child_cmd_wall,        0 },
197        { "opermsg",    1, ipc_child_cmd_opermsg,     0 },
198        { "rehash",     0, ipc_child_cmd_rehash,      0 },
199        { "kill",       2, ipc_child_cmd_kill,        0 },
200        { "hello",      0, ipc_child_cmd_hello,       0 },
201        { NULL }
202};
203
204
205static void ipc_command_exec( void *data, char **cmd, const command_t *commands )
206{
207        int i, j;
208       
209        if( !cmd[0] )
210                return;
211       
212        for( i = 0; commands[i].command; i ++ )
213                if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 )
214                {
215                        /* There is no typo in this line: */
216                        for( j = 1; cmd[j]; j ++ ); j --;
217                       
218                        if( j < commands[i].required_parameters )
219                                break;
220                       
221                        if( commands[i].flags & IPC_CMD_TO_CHILDREN )
222                                ipc_to_children( cmd );
223                        else
224                                commands[i].execute( data, cmd );
225                       
226                        break;
227                }
228}
229
230/* Return just one line. Returns NULL if something broke, an empty string
231   on temporary "errors" (EAGAIN and friends). */
232static char *ipc_readline( int fd )
233{
234        char buf[513], *eol;
235        int size;
236       
237        /* Because this is internal communication, it should be pretty safe
238           to just peek at the message, find its length (by searching for the
239           end-of-line) and then just read that message. With internal
240           sockets and limites message length, messages should always be
241           complete. Saves us quite a lot of code and buffering. */
242        size = recv( fd, buf, sizeof( buf ) - 1, MSG_PEEK );
243        if( size == 0 || ( size < 0 && !sockerr_again() ) )
244                return NULL;
245        else if( size < 0 ) /* && sockerr_again() */
246                return( g_strdup( "" ) );
247        else
248                buf[size] = 0;
249       
250        if( ( eol = strstr( buf, "\r\n" ) ) == NULL )
251                return NULL;
252        else
253                size = eol - buf + 2;
254       
255        if( recv( fd, buf, size, 0 ) != size )
256                return NULL;
257        else
258                return g_strndup( buf, size - 2 );
259}
260
261gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond )
262{
263        char *buf, **cmd;
264       
265        if( ( buf = ipc_readline( source ) ) )
266        {
267                cmd = irc_parse_line( buf );
268                if( cmd )
269                {
270                        ipc_command_exec( data, cmd, ipc_master_commands );
271                        g_free( cmd );
272                }
273                g_free( buf );
274        }
275        else
276        {
277                ipc_master_free_fd( source );
278        }
279       
280        return TRUE;
281}
282
283gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond )
284{
285        char *buf, **cmd;
286       
287        if( ( buf = ipc_readline( source ) ) )
288        {
289                cmd = irc_parse_line( buf );
290                if( cmd )
291                {
292                        ipc_command_exec( data, cmd, ipc_child_commands );
293                        g_free( cmd );
294                }
295                g_free( buf );
296        }
297        else
298        {
299                ipc_child_disable();
300        }
301       
302        return TRUE;
303}
304
305void ipc_to_master( char **cmd )
306{
307        if( global.conf->runmode == RUNMODE_FORKDAEMON )
308        {
309                char *s = irc_build_line( cmd );
310                ipc_to_master_str( "%s", s );
311                g_free( s );
312        }
313        else if( global.conf->runmode == RUNMODE_DAEMON )
314        {
315                ipc_command_exec( NULL, cmd, ipc_master_commands );
316        }
317}
318
319void ipc_to_master_str( char *format, ... )
320{
321        char *msg_buf;
322        va_list params;
323
324        va_start( params, format );
325        msg_buf = g_strdup_vprintf( format, params );
326        va_end( params );
327       
328        if( strlen( msg_buf ) > 512 )
329        {
330                /* Don't send it, it's too long... */
331        }
332        else if( global.conf->runmode == RUNMODE_FORKDAEMON )
333        {
334                if( global.listen_socket >= 0 )
335                        if( write( global.listen_socket, msg_buf, strlen( msg_buf ) ) <= 0 )
336                                ipc_child_disable();
337        }
338        else if( global.conf->runmode == RUNMODE_DAEMON )
339        {
340                char **cmd, *s;
341               
342                if( ( s = strchr( msg_buf, '\r' ) ) )
343                        *s = 0;
344               
345                cmd = irc_parse_line( msg_buf );
346                ipc_command_exec( NULL, cmd, ipc_master_commands );
347                g_free( cmd );
348        }
349       
350        g_free( msg_buf );
351}
352
353void ipc_to_children( char **cmd )
354{
355        if( global.conf->runmode == RUNMODE_FORKDAEMON )
356        {
357                char *msg_buf = irc_build_line( cmd );
358                ipc_to_children_str( "%s", msg_buf );
359                g_free( msg_buf );
360        }
361        else if( global.conf->runmode == RUNMODE_DAEMON )
362        {
363                GSList *l;
364               
365                for( l = irc_connection_list; l; l = l->next )
366                        ipc_command_exec( l->data, cmd, ipc_child_commands );
367        }
368}
369
370void ipc_to_children_str( char *format, ... )
371{
372        char *msg_buf;
373        va_list params;
374
375        va_start( params, format );
376        msg_buf = g_strdup_vprintf( format, params );
377        va_end( params );
378       
379        if( strlen( msg_buf ) > 512 )
380        {
381                /* Don't send it, it's too long... */
382        }
383        else if( global.conf->runmode == RUNMODE_FORKDAEMON )
384        {
385                int msg_len = strlen( msg_buf );
386                GSList *l, *next;
387               
388                for( l = child_list; l; l = next )
389                {
390                        struct bitlbee_child *c = l->data;
391                       
392                        next = l->next;
393                        if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 )
394                        {
395                                ipc_master_free_one( c );
396                                child_list = g_slist_remove( child_list, c );
397                        }
398                }
399        }
400        else if( global.conf->runmode == RUNMODE_DAEMON )
401        {
402                char **cmd, *s;
403               
404                if( ( s = strchr( msg_buf, '\r' ) ) )
405                        *s = 0;
406               
407                cmd = irc_parse_line( msg_buf );
408                ipc_to_children( cmd );
409                g_free( cmd );
410        }
411       
412        g_free( msg_buf );
413}
414
415void ipc_master_free_one( struct bitlbee_child *c )
416{
417        b_event_remove( c->ipc_inpa );
418        closesocket( c->ipc_fd );
419       
420        g_free( c->host );
421        g_free( c->nick );
422        g_free( c->realname );
423        g_free( c );
424}
425
426void ipc_master_free_fd( int fd )
427{
428        GSList *l;
429        struct bitlbee_child *c;
430       
431        for( l = child_list; l; l = l->next )
432        {
433                c = l->data;
434                if( c->ipc_fd == fd )
435                {
436                        ipc_master_free_one( c );
437                        child_list = g_slist_remove( child_list, c );
438                        break;
439                }
440        }
441}
442
443void ipc_master_free_all()
444{
445        GSList *l;
446       
447        for( l = child_list; l; l = l->next )
448                ipc_master_free_one( l->data );
449       
450        g_slist_free( child_list );
451        child_list = NULL;
452}
453
454void ipc_child_disable()
455{
456        b_event_remove( global.listen_watch_source_id );
457        close( global.listen_socket );
458       
459        global.listen_socket = -1;
460}
461
462#ifndef _WIN32
463char *ipc_master_save_state()
464{
465        char *fn = g_strdup( "/tmp/bee-restart.XXXXXX" );
466        int fd = mkstemp( fn );
467        GSList *l;
468        FILE *fp;
469        int i;
470       
471        if( fd == -1 )
472        {
473                log_message( LOGLVL_ERROR, "Could not create temporary file: %s", strerror( errno ) );
474                g_free( fn );
475                return NULL;
476        }
477       
478        /* This is more convenient now. */
479        fp = fdopen( fd, "w" );
480       
481        for( l = child_list, i = 0; l; l = l->next )
482                i ++;
483       
484        /* Number of client processes. */
485        fprintf( fp, "%d\n", i );
486       
487        for( l = child_list; l; l = l->next )
488                fprintf( fp, "%d %d\n", (int) ((struct bitlbee_child*)l->data)->pid,
489                                        ((struct bitlbee_child*)l->data)->ipc_fd );
490       
491        if( fclose( fp ) == 0 )
492        {
493                return fn;
494        }
495        else
496        {
497                unlink( fn );
498                g_free( fn );
499                return NULL;
500        }
501}
502
503
504static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond )
505{
506        struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 );
507       
508        child->ipc_fd = accept( serversock, NULL, 0 );
509       
510        if( child->ipc_fd == -1 )
511        {
512                log_message( LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno) );
513                return TRUE;
514        }
515               
516        child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
517       
518        child_list = g_slist_append( child_list, child );
519       
520        return TRUE;
521}
522
523int ipc_master_listen_socket()
524{
525        struct sockaddr_un un_addr;
526        int serversock;
527
528        /* Clean up old socket files that were hanging around.. */
529        if (unlink(IPCSOCKET) == -1 && errno != ENOENT) {
530                log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) );
531                return 0;
532        }
533
534        un_addr.sun_family = AF_UNIX;
535        strcpy(un_addr.sun_path, IPCSOCKET);
536
537        serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
538
539        if (serversock == -1) {
540                log_message( LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno) );
541                return 0;
542        }
543
544        if (bind(serversock, (struct sockaddr *)&un_addr, sizeof(un_addr)) == -1) {
545                log_message( LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno) );
546                return 0;
547        }
548
549        if (listen(serversock, 5) == -1) {
550                log_message( LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno) );
551                return 0;
552        }
553       
554        b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL );
555       
556        return 1;
557}
558#else
559int ipc_master_listen_socket()
560{
561        /* FIXME: Open named pipe \\.\BITLBEE */
562        return 0;
563}
564#endif
565
566int ipc_master_load_state( char *statefile )
567{
568        struct bitlbee_child *child;
569        FILE *fp;
570        int i, n;
571       
572        if( statefile == NULL )
573                return 0;
574       
575        fp = fopen( statefile, "r" );
576        unlink( statefile );    /* Why do it later? :-) */
577        if( fp == NULL )
578                return 0;
579       
580        if( fscanf( fp, "%d", &n ) != 1 )
581        {
582                log_message( LOGLVL_WARNING, "Could not import state information for child processes." );
583                fclose( fp );
584                return 0;
585        }
586       
587        log_message( LOGLVL_INFO, "Importing information for %d child processes.", n );
588        for( i = 0; i < n; i ++ )
589        {
590                child = g_new0( struct bitlbee_child, 1 );
591               
592                if( fscanf( fp, "%d %d", (int *) &child->pid, &child->ipc_fd ) != 2 )
593                {
594                        log_message( LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i );
595                        g_free( child );
596                        fclose( fp );
597                        return 0;
598                }
599                child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
600               
601                child_list = g_slist_append( child_list, child );
602        }
603       
604        ipc_to_children_str( "HELLO\r\n" );
605        ipc_to_children_str( "OPERMSG :New BitlBee master process started (version " BITLBEE_VERSION ")\r\n" );
606       
607        fclose( fp );
608        return 1;
609}
Note: See TracBrowser for help on using the repository browser.