2005-09-20 13:26:39 +00:00
/** \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
2006-01-24 17:20:31 +00:00
variables from ~ / . fishd , and takes care of communication between fish
2005-09-20 13:26:39 +00:00
instances . When no clients are running , fishd will automatically shut
down and save .
2006-06-20 00:50:10 +00:00
\ section fishd - commands Commands
2006-01-24 17:20:31 +00:00
Fishd works by sending and receiving commands . Each command is ended
with a newline . These are the commands supported by fishd :
< pre > set KEY : VALUE
set_export KEY : VALUE
< / pre >
These commands update the value of a variable . The only difference
between the two is that < tt > set_export < / tt > - variables should be
exported to children of the process using them . The variable value may
be escaped using C - style backslash escapes . In fact , this is required
for newline characters , which would otherwise be interpreted as end of
command .
< pre > erase KEY
< / pre >
Erase the variable with the specified name .
< pre > barrier
barrier_reply
< / pre >
A \ c barrier command will result in a barrier_reply being added to
the end of the senders queue of unsent messages . These commands are
used to synchronize clients , since once the reply for a barrier
message returns , the sender can know that any updates available at the
time the original barrier request was sent have been received .
2005-09-20 13:26:39 +00:00
*/
2006-01-23 16:25:34 +00:00
# include "config.h"
2006-02-28 13:17:16 +00:00
2005-09-20 13:26:39 +00:00
# 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>
2006-01-23 16:25:34 +00:00
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# endif
2005-09-20 13:26:39 +00:00
# include <errno.h>
# include <locale.h>
# include <signal.h>
2006-02-28 13:17:16 +00:00
# include "fallback.h"
2005-09-20 13:26:39 +00:00
# include "util.h"
2006-02-28 13:17:16 +00:00
2005-09-20 13:26:39 +00:00
# include "common.h"
# include "wutil.h"
# include "env_universal_common.h"
2005-09-23 13:10:31 +00:00
/**
Maximum length of socket filename
*/
2005-09-20 13:26:39 +00:00
# ifndef UNIX_PATH_MAX
# define UNIX_PATH_MAX 100
# endif
2006-01-26 09:57:06 +00:00
/**
Fallback if MSG_DONTWAIT isn ' t defined . That ' s actually prerry bad ,
and may lead to strange fishd behaviour , but at least it should
work most of the time .
*/
# ifndef MSG_DONTWAIT
# define MSG_DONTWAIT 0
# endif
2005-09-23 13:10:31 +00:00
/**
Small greeting to show that fishd is running
*/
2006-06-17 14:04:06 +00:00
# define GREETING "# Fish universal variable daemon\n"
2006-06-20 00:50:10 +00:00
/**
Small not about not editing ~ / . fishd manually . Inserted at the top of all . fishd files .
*/
2006-06-17 14:04:06 +00:00
# define SAVE_MSG "# This file is automatically generated by the fishd universal variable daemon.\n# Do NOT edit it directly, your changes will be overwritten.\n"
2005-09-20 13:26:39 +00:00
2005-09-23 13:10:31 +00:00
/**
The name of the save file . The hostname is appended to this .
*/
# define FILE ".fishd."
2005-09-20 13:26:39 +00:00
2005-09-23 13:10:31 +00:00
/**
Maximum length of hostname . Longer hostnames are truncated
*/
# define HOSTNAME_LEN 32
2005-09-28 01:43:09 +00:00
/**
The string to append to the socket name to name the lockfile
*/
# define LOCKPOSTFIX ".lock"
/**
The timeout in seconds on the lockfile for critical section
*/
# define LOCKTIMEOUT 1
2006-05-26 14:55:51 +00:00
/**
Getopt short switches for fishd
*/
# define GETOPT_STRING "hv"
2005-09-23 13:10:31 +00:00
/**
The list of connections to clients
*/
2005-09-20 13:26:39 +00:00
static connection_t * conn ;
2005-09-23 13:10:31 +00:00
/**
The socket to accept new clients on
*/
static int sock ;
2005-09-20 13:26:39 +00:00
2005-11-27 23:24:09 +00:00
/**
Set to one when fishd should save and exit
*/
static int quit = 0 ;
2006-01-23 16:25:34 +00:00
/**
Dynamically generated function , made from the documentation in doc_src .
*/
void print_help ( ) ;
2005-09-23 13:10:31 +00:00
/**
2005-09-28 01:43:09 +00:00
Constructs the fish socket filename
2005-09-23 13:10:31 +00:00
*/
2005-09-30 19:50:21 +00:00
static char * get_socket_filename ( )
2005-09-20 13:26:39 +00:00
{
char * name ;
2005-09-30 19:50:21 +00:00
char * dir = getenv ( " FISHD_SOCKET_DIR " ) ;
char * uname = getenv ( " USER " ) ;
2005-09-20 13:26:39 +00:00
2005-09-30 19:50:21 +00:00
if ( dir = = NULL )
{
2005-09-20 13:26:39 +00:00
dir = " /tmp " ;
2005-09-30 19:50:21 +00:00
}
2005-09-28 01:43:09 +00:00
2005-09-30 19:50:21 +00:00
if ( uname = = NULL )
{
2005-09-20 13:26:39 +00:00
struct passwd * pw ;
2005-09-30 19:50:21 +00:00
pw = getpwuid ( getuid ( ) ) ;
uname = strdup ( pw - > pw_name ) ;
2005-09-20 13:26:39 +00:00
}
2005-09-30 19:50:21 +00:00
name = malloc ( strlen ( dir ) + strlen ( uname ) + strlen ( SOCK_FILENAME ) + 2 ) ;
if ( name = = NULL )
{
wperror ( L " get_socket_filename " ) ;
exit ( EXIT_FAILURE ) ;
2005-09-28 01:43:09 +00:00
}
2005-09-30 19:50:21 +00:00
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 ( EXIT_FAILURE ) ;
2005-09-20 13:26:39 +00:00
}
2005-09-28 01:43:09 +00:00
return name ;
}
2005-11-27 23:24:09 +00:00
/**
Signal handler for the term signal .
*/
static void handle_term ( int signal )
{
quit = 1 ;
}
2005-09-28 01:43:09 +00:00
/**
2005-09-30 19:50:21 +00:00
Acquire the lock for the socket
Returns the name of the lock file if successful or
NULL if unable to obtain lock .
The returned string must be free ( ) d after unlink ( ) ing the file to release
the lock
2005-09-28 01:43:09 +00:00
*/
2005-09-30 19:50:21 +00:00
static char * acquire_socket_lock ( const char * sock_name )
2005-09-28 01:43:09 +00:00
{
2005-09-30 19:50:21 +00:00
int len = strlen ( sock_name ) ;
char * lockfile = malloc ( len + strlen ( LOCKPOSTFIX ) + 1 ) ;
if ( lockfile = = NULL )
{
wperror ( L " acquire_socket_lock " ) ;
exit ( EXIT_FAILURE ) ;
2005-09-28 01:43:09 +00:00
}
2005-09-30 19:50:21 +00:00
strcpy ( lockfile , sock_name ) ;
strcpy ( lockfile + len , LOCKPOSTFIX ) ;
if ( ! acquire_lock_file ( lockfile , LOCKTIMEOUT , 1 ) )
{
free ( lockfile ) ;
lockfile = NULL ;
2005-09-28 01:43:09 +00:00
}
2005-09-30 19:50:21 +00:00
return lockfile ;
2005-09-28 01:43:09 +00:00
}
/**
Connects to the fish socket and starts listening for connections
*/
2005-09-30 19:50:21 +00:00
static int get_socket ( )
2005-09-28 01:43:09 +00:00
{
int s , len , doexit = 0 ;
int exitcode = EXIT_FAILURE ;
struct sockaddr_un local ;
char * sock_name = get_socket_filename ( ) ;
2005-09-30 19:50:21 +00:00
/*
Start critical section protected by lock
2005-09-28 01:43:09 +00:00
*/
2005-09-30 19:50:21 +00:00
char * lockfile = acquire_socket_lock ( sock_name ) ;
if ( lockfile = = NULL )
{
2005-10-01 09:57:09 +00:00
debug ( 0 , L " Unable to obtain lock on socket, exiting " ) ;
2005-09-30 19:50:21 +00:00
exit ( EXIT_FAILURE ) ;
}
2006-06-13 15:39:40 +00:00
debug ( 4 , L " Acquired lockfile: %s " , lockfile ) ;
2005-09-20 13:26:39 +00:00
2005-09-28 01:43:09 +00:00
local . sun_family = AF_UNIX ;
2005-09-30 19:50:21 +00:00
strcpy ( local . sun_path , sock_name ) ;
2006-01-09 14:44:18 +00:00
len = sizeof ( local ) ;
2005-09-28 01:43:09 +00:00
debug ( 1 , L " Connect to socket at %s " , sock_name ) ;
2005-09-20 13:26:39 +00:00
2005-09-30 19:50:21 +00:00
if ( ( s = socket ( AF_UNIX , SOCK_STREAM , 0 ) ) = = - 1 )
{
wperror ( L " socket " ) ;
2005-09-28 01:43:09 +00:00
doexit = 1 ;
goto unlock ;
2005-09-20 13:26:39 +00:00
}
2005-09-30 19:50:21 +00:00
/*
2005-09-28 01:43:09 +00:00
First check whether the socket has been opened by another fishd ;
if so , exit with success status
*/
2005-09-30 19:50:21 +00:00
if ( connect ( s , ( struct sockaddr * ) & local , len ) = = 0 )
{
debug ( 1 , L " Socket already exists, exiting " ) ;
2005-09-28 01:43:09 +00:00
doexit = 1 ;
exitcode = 0 ;
goto unlock ;
}
2005-09-20 13:26:39 +00:00
2005-09-30 19:50:21 +00:00
unlink ( local . sun_path ) ;
if ( bind ( s , ( struct sockaddr * ) & local , len ) = = - 1 )
{
wperror ( L " bind " ) ;
2005-09-28 01:43:09 +00:00
doexit = 1 ;
goto unlock ;
2005-09-20 13:26:39 +00:00
}
2005-09-28 01:43:09 +00:00
2005-09-30 19:50:21 +00:00
if ( fcntl ( s , F_SETFL , O_NONBLOCK ) ! = 0 )
{
2005-09-20 13:26:39 +00:00
wperror ( L " fcntl " ) ;
2005-09-28 01:43:09 +00:00
close ( s ) ;
doexit = 1 ;
2005-09-30 19:50:21 +00:00
} else if ( listen ( s , 64 ) = = - 1 )
{
wperror ( L " listen " ) ;
2005-09-28 01:43:09 +00:00
doexit = 1 ;
2005-09-20 13:26:39 +00:00
}
2005-09-28 01:43:09 +00:00
unlock :
2005-09-30 19:50:21 +00:00
( void ) unlink ( lockfile ) ;
2006-06-13 15:39:40 +00:00
debug ( 4 , L " Released lockfile: %s " , lockfile ) ;
2005-09-30 19:50:21 +00:00
/*
End critical section protected by lock
2005-09-28 01:43:09 +00:00
*/
2005-09-30 19:50:21 +00:00
free ( lockfile ) ;
2005-09-20 13:26:39 +00:00
2005-09-30 19:50:21 +00:00
free ( sock_name ) ;
2005-09-28 01:43:09 +00:00
2005-09-30 19:50:21 +00:00
if ( doexit )
{
exit ( exitcode ) ;
}
2005-09-28 01:43:09 +00:00
2005-09-20 13:26:39 +00:00
return s ;
}
2005-09-23 13:10:31 +00:00
/**
Event handler . Broadcasts updates to all clients .
*/
static void broadcast ( int type , const wchar_t * key , const wchar_t * val )
2005-09-20 13:26:39 +00:00
{
connection_t * c ;
message_t * msg ;
if ( ! conn )
return ;
2005-10-07 09:17:59 +00:00
2005-09-22 20:16:52 +00:00
msg = create_message ( type , key , val ) ;
2005-09-20 13:26:39 +00:00
/*
2005-10-07 09:17:59 +00:00
Don ' t merge these loops , or try_send_all can free the message
prematurely
2005-09-20 13:26:39 +00:00
*/
2005-09-22 20:16:52 +00:00
2005-09-20 13:26:39 +00:00
for ( c = conn ; c ; c = c - > next )
{
msg - > count + + ;
q_put ( & c - > unsent , msg ) ;
}
2005-09-22 20:16:52 +00:00
2005-09-20 13:26:39 +00:00
for ( c = conn ; c ; c = c - > next )
{
try_send_all ( c ) ;
}
}
2005-09-23 13:10:31 +00:00
/**
Make program into a creature of the night .
*/
static void daemonize ( )
2005-09-20 13:26:39 +00:00
{
/*
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 :
{
2005-11-23 18:57:43 +00:00
/*
2005-11-27 23:22:08 +00:00
Make fishd ignore the HUP signal .
2005-11-23 18:57:43 +00:00
*/
2005-09-20 13:26:39 +00:00
struct sigaction act ;
sigemptyset ( & act . sa_mask ) ;
act . sa_flags = 0 ;
act . sa_handler = SIG_IGN ;
sigaction ( SIGHUP , & act , 0 ) ;
2005-11-27 23:24:09 +00:00
/*
Make fishd save and exit on the TERM signal .
*/
sigfillset ( & act . sa_mask ) ;
act . sa_flags = 0 ;
act . sa_handler = & handle_term ;
sigaction ( SIGTERM , & act , 0 ) ;
2005-09-20 13:26:39 +00:00
break ;
}
default :
{
2005-11-23 18:57:43 +00:00
debug ( 0 , L " Parent process exiting (This is normal) " ) ;
2005-09-20 13:26:39 +00:00
exit ( 0 ) ;
}
}
2005-11-27 23:22:08 +00:00
2005-09-20 13:26:39 +00:00
/*
Put ourself in out own processing group
*/
2005-10-07 09:17:16 +00:00
setsid ( ) ;
2005-11-27 23:22:08 +00:00
2005-09-20 13:26:39 +00:00
/*
2005-10-07 09:17:59 +00:00
Close stdin and stdout . We only use stderr , anyway .
2005-09-20 13:26:39 +00:00
*/
close ( 0 ) ;
close ( 1 ) ;
}
2005-09-23 13:10:31 +00:00
/**
Load or save all variables
*/
void load_or_save ( int save )
2005-09-20 13:26:39 +00:00
{
struct passwd * pw ;
char * name ;
char * dir = getenv ( " HOME " ) ;
2005-09-23 13:10:31 +00:00
char hostname [ HOSTNAME_LEN ] ;
connection_t c ;
2005-09-20 13:26:39 +00:00
if ( ! dir )
{
pw = getpwuid ( getuid ( ) ) ;
dir = pw - > pw_dir ;
}
2005-09-23 13:10:31 +00:00
gethostname ( hostname , HOSTNAME_LEN ) ;
name = malloc ( strlen ( dir ) + strlen ( FILE ) + strlen ( hostname ) + 2 ) ;
2005-09-20 13:26:39 +00:00
strcpy ( name , dir ) ;
strcat ( name , " / " ) ;
strcat ( name , FILE ) ;
2005-09-23 13:10:31 +00:00
strcat ( name , hostname ) ;
2005-09-20 13:26:39 +00:00
2006-06-13 15:39:40 +00:00
debug ( 4 , L " Open file for %s: '%s' " ,
2005-09-23 13:10:31 +00:00
save ? " saving " : " loading " ,
name ) ;
2005-09-20 13:26:39 +00:00
2005-09-23 13:10:31 +00:00
c . fd = open ( name , save ? ( O_CREAT | O_TRUNC | O_WRONLY ) : O_RDONLY , 0600 ) ;
2005-09-20 13:26:39 +00:00
free ( name ) ;
2005-09-23 13:10:31 +00:00
if ( c . fd = = - 1 )
2005-09-20 13:26:39 +00:00
{
2005-09-23 13:10:31 +00:00
debug ( 1 , L " Could not open load/save file. No previous saves? " ) ;
wperror ( L " open " ) ;
2005-10-07 09:17:59 +00:00
return ;
2005-09-20 13:26:39 +00:00
}
2006-06-13 15:39:40 +00:00
debug ( 4 , L " File open on fd %d " , c . fd ) ;
2005-09-23 13:10:31 +00:00
sb_init ( & c . input ) ;
memset ( & c . wstate , ' \0 ' , sizeof ( mbstate_t ) ) ;
q_init ( & c . unsent ) ;
if ( save )
2006-06-17 14:04:06 +00:00
{
write ( c . fd , SAVE_MSG , strlen ( SAVE_MSG ) ) ;
2005-09-23 13:10:31 +00:00
enqueue_all ( & c ) ;
2006-06-17 14:04:06 +00:00
}
2005-09-23 13:10:31 +00:00
else
read_message ( & c ) ;
q_destroy ( & c . unsent ) ;
sb_destroy ( & c . input ) ;
close ( c . fd ) ;
2005-09-20 13:26:39 +00:00
}
2005-10-24 15:26:25 +00:00
/**
Load variables from disk
*/
2005-09-23 13:10:31 +00:00
static void load ( )
2005-09-20 13:26:39 +00:00
{
2005-09-23 13:10:31 +00:00
load_or_save ( 0 ) ;
}
2005-09-20 13:26:39 +00:00
2005-10-24 15:26:25 +00:00
/**
Save variables to disk
*/
2005-09-23 13:10:31 +00:00
static void save ( )
{
load_or_save ( 1 ) ;
2005-09-20 13:26:39 +00:00
}
2005-09-23 13:10:31 +00:00
/**
Do all sorts of boring initialization .
*/
2005-09-20 13:26:39 +00:00
static void init ( )
{
2005-09-28 01:43:09 +00:00
2005-09-20 13:26:39 +00:00
sock = get_socket ( ) ;
daemonize ( ) ;
env_universal_common_init ( & broadcast ) ;
load ( ) ;
}
2005-10-24 15:26:25 +00:00
/**
Main function for fishd
*/
2005-09-20 13:26:39 +00:00
int main ( int argc , char * * argv )
{
2005-11-27 23:24:09 +00:00
int child_socket ;
2005-09-20 13:26:39 +00:00
struct sockaddr_un remote ;
2005-11-27 23:24:09 +00:00
socklen_t t ;
2005-09-20 13:26:39 +00:00
int max_fd ;
int update_count = 0 ;
fd_set read_fd , write_fd ;
2006-01-23 16:25:34 +00:00
program_name = L " fishd " ;
wsetlocale ( LC_ALL , L " " ) ;
/*
Parse options
*/
while ( 1 )
{
static struct option
long_options [ ] =
{
{
" help " , no_argument , 0 , ' h '
}
,
{
" version " , no_argument , 0 , ' v '
}
,
{
0 , 0 , 0 , 0
}
}
;
int opt_index = 0 ;
int opt = getopt_long ( argc ,
argv ,
2006-05-26 14:55:51 +00:00
GETOPT_STRING ,
2006-01-23 16:25:34 +00:00
long_options ,
& opt_index ) ;
if ( opt = = - 1 )
break ;
switch ( opt )
{
case 0 :
break ;
case ' h ' :
print_help ( ) ;
exit ( 0 ) ;
case ' v ' :
debug ( 0 , L " %ls, version %s \n " , program_name , PACKAGE_VERSION ) ;
exit ( 0 ) ;
case ' ? ' :
return 1 ;
}
}
2005-09-20 13:26:39 +00:00
2006-01-23 16:25:34 +00:00
init ( ) ;
2005-09-20 13:26:39 +00:00
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 ) ;
}
}
2005-11-27 23:24:09 +00:00
while ( 1 )
2005-09-20 13:26:39 +00:00
{
2005-11-27 23:24:09 +00:00
res = select ( max_fd , & read_fd , & write_fd , 0 , 0 ) ;
if ( quit )
{
save ( ) ;
exit ( 0 ) ;
}
if ( res ! = - 1 )
break ;
if ( errno ! = EINTR )
{
wperror ( L " select " ) ;
exit ( 1 ) ;
}
2005-09-20 13:26:39 +00:00
}
2005-11-27 23:22:08 +00:00
2005-09-20 13:26:39 +00:00
if ( FD_ISSET ( sock , & read_fd ) )
{
if ( ( child_socket =
accept ( sock ,
( struct sockaddr * ) & remote ,
& t ) ) = = - 1 ) {
wperror ( L " accept " ) ;
exit ( 1 ) ;
}
else
{
2006-06-13 15:39:40 +00:00
debug ( 4 , L " Connected with new child on fd %d " , child_socket ) ;
2005-09-20 13:26:39 +00:00
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 + + ;
2005-09-23 13:10:31 +00:00
if ( update_count > = 64 )
2005-09-20 13:26:39 +00:00
{
save ( ) ;
update_count = 0 ;
}
}
}
connection_t * prev = 0 ;
c = conn ;
while ( c )
{
if ( c - > killme )
{
2006-06-13 15:39:40 +00:00
debug ( 4 , L " Close connection %d " , c - > fd ) ;
2005-09-20 13:26:39 +00:00
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 ;
2005-10-07 09:17:59 +00:00
}
2005-09-20 13:26:39 +00:00
}
}