From 6f08ee51e8ffafd997b131e74ffe6b6d0ab2db56 Mon Sep 17 00:00:00 2001 From: netocrat Date: Wed, 28 Sep 2005 11:43:09 +1000 Subject: [PATCH] Critical section locking for fishd darcs-hash:20050928014309-344c5-92a1e5aa31eceacc4f76cdb2638358d1014c2e3f.gz --- common.c | 41 ++++++++++++++ common.h | 2 + fishd.c | 163 +++++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 164 insertions(+), 42 deletions(-) diff --git a/common.c b/common.c index 376b16209..ee9b2d9f5 100644 --- a/common.c +++ b/common.c @@ -19,6 +19,8 @@ parts of fish. #include #include #include +#include +#include #if HAVE_NCURSES_H #include @@ -52,6 +54,12 @@ parts of fish. */ #define ERROR_MAX_COUNT 1 +/** + The number of nanoseconds to wait between polls when attempting to acquire + a lockfile +*/ +#define LOCKPOLLINTERVAL 1000000 + struct termios shell_modes; /** @@ -1062,3 +1070,36 @@ wchar_t *unescape( wchar_t * in, int escape_special ) return in; } +/** + Attempt to acquire a lock based on a lockfile, waiting LOCKPOLLINTERVAL + nanoseconds between polls and timing out after timeout seconds, + thereafter forcibly attempting to obtain the lock. Returns the opened + lockfile's descriptor or -1 if not possible to open the file or on error. + Contains a race condition when the lockfile is on an NFS filesystem + according to Linux manpage open(2) +*/ +int acquire_lock_file(const char *lockfile, const int timeout) +{ + int ret = -1; + struct timespec pollint; + struct timeval start, end; + double elapsed; + + if (gettimeofday(&start, NULL) == 0) { + pollint.tv_sec = 0; + pollint.tv_nsec = LOCKPOLLINTERVAL; + while ((ret = open(lockfile, O_EXCL|O_CREAT|O_RDONLY)) == -1) { + if (gettimeofday(&end, NULL) != 0) + break; + elapsed = end.tv_sec + end.tv_usec/1000000.0 - + (start.tv_sec + start.tv_usec/1000000.0); + if (elapsed >= timeout) { + (void)unlink(lockfile); + ret = open(lockfile, O_EXCL|O_CREAT|O_RDONLY); + break; + } + (void)nanosleep(&pollint, NULL); + } + } + return ret; +} diff --git a/common.h b/common.h index af835d602..9e2ea82e2 100644 --- a/common.h +++ b/common.h @@ -246,3 +246,5 @@ wchar_t *unescape( wchar_t * in, int escape_special ); void block(); void unblock(); + +int acquire_lock_file(const char *lockfile, const int timeout); diff --git a/fishd.c b/fishd.c index f22b37ccc..8d26a109f 100644 --- a/fishd.c +++ b/fishd.c @@ -50,6 +50,16 @@ down and save. */ #define HOSTNAME_LEN 32 +/** + 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 + /** The list of connections to clients */ @@ -61,55 +71,112 @@ static connection_t *conn; static int sock; /** - Connects to the fish socket + Constructs the fish socket filename */ -static int get_socket() +static char *get_socket_filename(void) { - int s, len; - struct sockaddr_un local; char *name; - char *dir = getenv( "FISHD_SOCKET_DIR" ); - char *uname = getenv( "USER" ); + char *dir = getenv("FISHD_SOCKET_DIR"); + char *uname = getenv("USER"); - if( !dir ) + if (dir == NULL) 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); + if (uname == NULL) { + struct passwd *pw; + pw = getpwuid(getuid()); + uname = strdup(pw->pw_name); } + + name = malloc(strlen(dir)+ strlen(uname)+ strlen(SOCK_FILENAME) + 2); + if (name == NULL) { + perror("get_socket_filename"); + exit(EXIT_FAILURE); + } + 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); + } + return name; +} + +/** + Acquire the lock file for the socket + Returns an fd that should be closed to unlock +*/ +int acquire_socket_lock_file(const char *sock_name, char **lockfile) +{ + int fd; + int len = strlen(sock_name); + + *lockfile = malloc(len + strlen(LOCKPOSTFIX) + 1); + if (*lockfile == NULL) { + perror("get_socket"); + exit(EXIT_FAILURE); + } + (void)strcpy(*lockfile, sock_name); + (void)strcpy(*lockfile + len, LOCKPOSTFIX); + if ((fd = acquire_lock_file(*lockfile, LOCKTIMEOUT)) == -1) { + fprintf(stderr, "Unable to obtain lockfile %s, exiting", + *lockfile); + exit(EXIT_FAILURE); + } + return fd; +} + +/** + Connects to the fish socket and starts listening for connections +*/ +static int get_socket(void) +{ + int s, len, doexit = 0; + int exitcode = EXIT_FAILURE; + struct sockaddr_un local; + char *lockfile; + char *sock_name = get_socket_filename(); + int fd_lockfile; + + /** + Start critical section protected by lock file + */ + fd_lockfile = acquire_socket_lock_file(sock_name, &lockfile); + debug(1, L"Acquired lockfile %s", lockfile); - debug( 1, L"Connect to socket at %s", name ); + local.sun_family = AF_UNIX; + strcpy(local.sun_path, sock_name); + len = strlen(local.sun_path) + sizeof(local.sun_family); + + debug(1, L"Connect to socket at %s", sock_name); if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("socket"); - exit(1); + doexit = 1; + goto unlock; + } + + /** + First check whether the socket has been opened by another fishd; + if so, exit with success status + */ + if (connect(s, (struct sockaddr *)&local, len) == 0) { + debug(1, L"Socket already exists, exiting"); + doexit = 1; + exitcode = 0; + goto unlock; } - 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); + doexit = 1; + goto unlock; } + /* if( setsockopt( s, SOL_SOCKET, SO_PASSCRED, &on, sizeof( on ) ) ) { @@ -118,14 +185,30 @@ static int get_socket() } */ - if( fcntl( s, F_SETFL, O_NONBLOCK ) != 0 ) - { + if( fcntl( s, F_SETFL, O_NONBLOCK ) != 0 ) { wperror( L"fcntl" ); - close( s ); - return -1; - + close( s ); + doexit = 1; + } else if (listen(s, 64) == -1) { + wperror(L"listen"); + doexit = 1; } + +unlock: + (void)close(fd_lockfile); + (void)unlink(lockfile); + debug(1, L"Released lockfile %s", lockfile); + /** + End critical section protected by lock file + */ + free(lockfile); + + free(sock_name); + + if (doexit) + exit(exitcode); + return s; } @@ -277,12 +360,8 @@ static void save() static void init() { program_name=L"fishd"; + sock = get_socket(); - if (listen(sock, 64) == -1) - { - wperror(L"listen"); - exit(1); - } daemonize();