diff options
author | axel <axel@liljencrantz.se> | 2005-09-20 23:26:39 +1000 |
---|---|---|
committer | axel <axel@liljencrantz.se> | 2005-09-20 23:26:39 +1000 |
commit | 149594f974350bb364a76c73b91b1d5ffddaa1fa (patch) | |
tree | 95650e9982d5fabe4bd805d94c5d700cbbc1ca7f /fishd.c |
Initial revision
darcs-hash:20050920132639-ac50b-fa3b476891e1f5f67207cf4cc7bf623834cc5edc.gz
Diffstat (limited to 'fishd.c')
-rw-r--r-- | fishd.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/fishd.c b/fishd.c new file mode 100644 index 00000000..30e93b8b --- /dev/null +++ b/fishd.c @@ -0,0 +1,443 @@ +/** \file fishd.c + +The universal variable server. fishd is automatically started by fish +if a fishd server isn't already running. fishd reads any saved +variables from ~/.fishd, and takes care of commonication between fish +instances. When no clients are running, fishd will automatically shut +down and save. + +*/ +#include <stdio.h> +#include <stdlib.h> +#include <wchar.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <pwd.h> +#include <fcntl.h> + +#include <errno.h> +#include <locale.h> +#include <signal.h> + +#include "util.h" +#include "common.h" +#include "wutil.h" +#include "env_universal_common.h" + + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 100 +#endif + +#define GREETING "#Fish universal variable daemon\n#Lines beginning with '#' are ignored\n#Syntax:\n#SET VARNAME:VALUE\n#or\n#ERASE VARNAME\n#Where VALUE is the escaped value of the variable\n#Backslash escapes and \\xxx hexadecimal style escapes are supported\n" +#define FILE ".fishd" + + +static connection_t *conn; +static int sock; + + +int get_socket() +{ + int s, len; + struct sockaddr_un local; + char *name; + char *dir = getenv( "FISHD_SOCKET_DIR" ); + char *uname = getenv( "USER" ); + + if( !dir ) + dir = "/tmp"; + if( uname==0 ) + { + struct passwd *pw; + pw = getpwuid( getuid() ); + uname = strdup( pw->pw_name ); + } + + name = malloc( strlen(dir)+ strlen(uname)+ strlen(SOCK_FILENAME) + 2 ); + strcpy( name, dir ); + strcat( name, "/" ); + strcat( name, SOCK_FILENAME ); + strcat( name, uname ); + + if( strlen( name ) >= UNIX_PATH_MAX ) + { + debug( 1, L"Filename too long: '%s'", name ); + exit(1); + } + + debug( 1, L"Connect to socket at %s", name ); + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket"); + exit(1); + } + + local.sun_family = AF_UNIX; + strcpy(local.sun_path, name ); + unlink(local.sun_path); + len = strlen(local.sun_path) + sizeof(local.sun_family); + + free( name ); + + if (bind(s, (struct sockaddr *)&local, len) == -1) { + perror("bind"); + exit(1); + } +/* + if( setsockopt( s, SOL_SOCKET, SO_PASSCRED, &on, sizeof( on ) ) ) + { + perror( "setsockopt"); + exit(1); + } +*/ + + if( fcntl( s, F_SETFL, O_NONBLOCK ) != 0 ) + { + wperror( L"fcntl" ); + close( s ); + return -1; + + } + + return s; +} + +void enqueue( const void *k, + const void *v, + void *q) +{ + const wchar_t *key = (const wchar_t *)k; + const wchar_t *val = (const wchar_t *)v; + queue_t *queue = (queue_t *)q; + + message_t *msg = create_message( SET, key, val ); + msg->count=1; + + q_put( queue, msg ); +} + +void enqueue_all( connection_t *c ) +{ + hash_foreach2( &env_universal_var, + &enqueue, + (void *)&c->unsent ); + try_send_all( c ); +} + +void broadcast( int type, const wchar_t *key, const wchar_t *val ) +{ + connection_t *c; + message_t *msg; + + if( !conn ) + return; + + msg = create_message( type, key, val ); + + /* + Don't merge loops, or try_send_all can free the message prematurely + */ + + for( c = conn; c; c=c->next ) + { + msg->count++; + q_put( &c->unsent, msg ); + } + + for( c = conn; c; c=c->next ) + { + try_send_all( c ); + } +} + +void daemonize() +{ + /* + Fork, and let parent exit + */ + switch( fork() ) + { + case -1: + debug( 0, L"Could not put fishd in background. Quitting" ); + wperror( L"fork" ); + exit(1); + + case 0: + { + struct sigaction act; + sigemptyset( & act.sa_mask ); + act.sa_flags=0; + act.sa_handler=SIG_IGN; + sigaction( SIGHUP, &act, 0); + break; + } + + default: + { + debug( 0, L"Parent calling exit" ); + exit(0); + } + } + + /* + Put ourself in out own processing group + */ + setpgrp(); + + /* + Close stdin and stdout + */ + close( 0 ); + close( 1 ); + +} + + +void load() +{ + struct passwd *pw; + char *name; + char *dir = getenv( "HOME" ); + if( !dir ) + { + pw = getpwuid( getuid() ); + dir = pw->pw_dir; + } + + name = malloc( strlen(dir)+ strlen(FILE)+ 2 ); + strcpy( name, dir ); + strcat( name, "/" ); + strcat( name, FILE ); + + debug( 1, L"Open file for loading: '%s'", name ); + + connection_t load; + load.fd = open( name, O_RDONLY); + + free( name ); + + if( load.fd == -1 ) + { + debug( 0, L"Could not open save file. No previous saves?" ); + } + debug( 1, L"Load input file on fd %d", load.fd ); + sb_init( &load.input ); + memset (&load.wstate, '\0', sizeof (mbstate_t)); + read_message( &load ); + sb_destroy( &load.input ); + close( load.fd ); +} + +void save() +{ + struct passwd *pw; + char *name; + char *dir = getenv( "HOME" ); + if( !dir ) + { + pw = getpwuid( getuid() ); + dir = pw->pw_dir; + } + + name = malloc( strlen(dir)+ strlen(FILE)+ 2 ); + strcpy( name, dir ); + strcat( name, "/" ); + strcat( name, FILE ); + + debug( 1, L"Open file for saving: '%s'", name ); + + connection_t save; + save.fd = open( name, O_CREAT | O_TRUNC | O_WRONLY); + free( name ); + + if( save.fd == -1 ) + { + debug( 0, L"Could not open save file" ); + wperror( L"open" ); + exit(1); + } + debug( 1, L"File open on fd %d'", save.fd ); + q_init( &save.unsent ); + enqueue_all( &save ); + close( save.fd ); + q_destroy( &save.unsent ); +} + +static void init() +{ + program_name=L"fishd"; + sock = get_socket(); + if (listen(sock, 64) == -1) + { + wperror(L"listen"); + exit(1); + } + + daemonize(); + + fish_setlocale( LC_ALL, L"" ); + + env_universal_common_init( &broadcast ); + + load(); +} + +int main( int argc, char ** argv ) +{ + int child_socket, t; + struct sockaddr_un remote; + int max_fd; + int update_count=0; + + fd_set read_fd, write_fd; + + init(); + + while(1) + { + connection_t *c; + int res; + + t=sizeof( remote ); + + FD_ZERO( &read_fd ); + FD_ZERO( &write_fd ); + FD_SET( sock, &read_fd ); + max_fd = sock+1; + for( c=conn; c; c=c->next ) + { + FD_SET( c->fd, &read_fd ); + max_fd = maxi( max_fd, c->fd+1); + + if( ! q_empty( &c->unsent ) ) + { + FD_SET( c->fd, &write_fd ); + } + } + + res=select( max_fd, &read_fd, &write_fd, 0, 0 ); + + if( res==-1 ) + { + wperror( L"select" ); + exit(1); + } + + if( FD_ISSET( sock, &read_fd ) ) + { + if( (child_socket = + accept( sock, + (struct sockaddr *)&remote, + &t) ) == -1) { + wperror( L"accept" ); + exit(1); + } + else + { + debug( 1, L"Connected with new child on fd %d", child_socket ); + + if( fcntl( child_socket, F_SETFL, O_NONBLOCK ) != 0 ) + { + wperror( L"fcntl" ); + close( child_socket ); + } + else + { + connection_t *new = malloc( sizeof(connection_t)); + new->fd = child_socket; + new->next = conn; + q_init( &new->unsent ); + new->killme=0; + sb_init( &new->input ); + memset (&new->wstate, '\0', sizeof (mbstate_t)); + send( new->fd, GREETING, strlen(GREETING), MSG_DONTWAIT ); + enqueue_all( new ); + conn=new; + } + } + } + + for( c=conn; c; c=c->next ) + { + if( FD_ISSET( c->fd, &write_fd ) ) + { + try_send_all( c ); + } + } + + for( c=conn; c; c=c->next ) + { + if( FD_ISSET( c->fd, &read_fd ) ) + { + read_message( c ); + + /* + Occasionally we save during normal use, so that we + won't lose everything on a system crash + */ + update_count++; + if( update_count >= 8 ) + { + save(); + update_count = 0; + } + } + } + + connection_t *prev=0; + c=conn; + + while( c ) + { + if( c->killme ) + { + debug( 1, L"Close connection %d", c->fd ); + + close(c->fd ); + sb_destroy( &c->input ); + + while( !q_empty( &c->unsent ) ) + { + message_t *msg = (message_t *)q_get( &c->unsent ); + msg->count--; + if( !msg->count ) + free( msg ); + } + + q_destroy( &c->unsent ); + if( prev ) + { + prev->next=c->next; + } + else + { + conn=c->next; + } + + free(c); + + c=(prev?prev->next:conn); + + } + else + { + prev=c; + c=c->next; + } + } + if( !conn ) + { + debug( 0, L"No more clients. Quitting" ); + save(); + env_universal_common_destroy(); + exit(0); + c=c->next; + } + + } +} |