diff --git a/.hgignore b/.hgignore index b254ad8..d5366d6 100644 --- a/.hgignore +++ b/.hgignore @@ -4,3 +4,6 @@ ^build/ ^pkg/ \.orig$ +.*\.swp$ +cscope.out$ +valgrind.out$ diff --git a/src/self_pipe.c b/src/self_pipe.c new file mode 100644 index 0000000..992fa31 --- /dev/null +++ b/src/self_pipe.c @@ -0,0 +1,140 @@ +/** + * self_pipe.c + * + * author: Alex Young + * + * Wrapper for the self-pipe trick for select()-based thread + * synchronisation. Get yourself a self_pipe with self_pipe_create(), + * select() on the read end of the pipe with the help of + * self_pipe_fd_set( sig, fds ) and self_pipe_fd_isset( sig, fds ). + * When you've received a signal, clear it with + * self_pipe_signal_clear(sig) so that the buffer doesn't get filled. + * + */ + + +#include +#include +#include +#include +#include +#include + + +#include "util.h" +#include "self_pipe.h" + +#define ERR_MSG_PIPE "Couldn't open a pipe for signaling." +#define ERR_MSG_FCNTL "Couldn't set a signalling pipe non-blocking." +#define ERR_MSG_WRITE "Couldn't write to a signaling pipe." +#define ERR_MSG_READ "Couldn't read from a signaling pipe." + +void self_pipe_server_error( int err, char *msg ) +{ + char errbuf[1024]; + + strerror_r( err, errbuf, 1024 ); + + debug(msg); + SERVER_ERROR( "%s\t%s", msg, errbuf ); +} + +/** + * Allocate a struct self_pipe, opening the pipe and allocating a + * pthread mutex. + * + * Returns NULL if the pipe couldn't be opened or if we couldn't set it + * non-blocking. + * + * Remember to call self_pipe_destroy when you're done with the return + * value. + */ +struct self_pipe * self_pipe_create(void) +{ + struct self_pipe *sig = xmalloc( sizeof( struct self_pipe ) ); + int fds[2]; + int fcntl_err; + + if ( NULL == sig ) { return NULL; } + + if ( pipe( fds ) ) { + free( sig ); + self_pipe_server_error( errno, ERR_MSG_PIPE ); + return NULL; + } + + if ( fcntl( fds[0], F_SETFD, O_NONBLOCK ) || fcntl( fds[1], F_SETFD, O_NONBLOCK ) ) { + fcntl_err = errno; + while( close( fds[0] ) == -1 && errno == EINTR ); + while( close( fds[1] ) == -1 && errno == EINTR ); + free( sig ); + self_pipe_server_error( fcntl_err, ERR_MSG_FCNTL ); + + return NULL; + } + + sig->read_fd = fds[0]; + sig->write_fd = fds[1]; + + return sig; +} + + +/** + * Send a signal to anyone select()ing on this signal. + * + * Returns 1 on success. Can fail if weirdness happened to the write fd + * of the pipe in the self_pipe struct. + */ +int self_pipe_signal( struct self_pipe * sig ) +{ + if ( write( sig->write_fd, "1", 1 ) != 1 ) { + self_pipe_server_error( errno, ERR_MSG_WRITE ); + return 0; + } + + return 1; +} + + +/** + * Clear a received signal from the pipe. Every signal sent must be + * cleared by one (and only one) recipient when they return from select(). + */ +int self_pipe_signal_clear( struct self_pipe *sig ) +{ + char buf[1]; + + read( sig->read_fd, buf, 1 ); + return 1; +} + +/** + * Close the pipe and free the self_pipe. Do not try to use the + * self_pipe struct after calling this, the innards are mush. + */ +int self_pipe_destroy( struct self_pipe * sig ) +{ + while( close( sig->read_fd ) == -1 && errno == EINTR ); + while( close( sig->write_fd ) == -1 && errno == EINTR ); + + /* Just in case anyone *does* try to use this after free, + * we should set the memory locations to an error value + */ + sig->read_fd = -1; + sig->write_fd = -1; + + free( sig ); + return 1; +} + +int self_pipe_fd_set( struct self_pipe * sig, fd_set * fds) +{ + FD_SET( sig->read_fd, fds ); + return 1; +} + +int self_pipe_fd_isset( struct self_pipe * sig, fd_set * fds) +{ + return FD_ISSET( sig->read_fd, fds ); +} diff --git a/src/self_pipe.h b/src/self_pipe.h new file mode 100644 index 0000000..17b3269 --- /dev/null +++ b/src/self_pipe.h @@ -0,0 +1,16 @@ +#define SELF_PIPE_H + +#include + +struct self_pipe { + int read_fd; + int write_fd; +}; + + +struct self_pipe * self_pipe_create(void); +int self_pipe_signal( struct self_pipe * sig ); +int self_pipe_signal_clear( struct self_pipe *sig ); +int self_pipe_destroy( struct self_pipe * sig ); +int self_pipe_fd_set( struct self_pipe * sig, fd_set * fds ); +int self_pipe_fd_isset( struct self_pipe *sig, fd_set *fds ); diff --git a/tests/check_self_pipe.c b/tests/check_self_pipe.c new file mode 100644 index 0000000..f1d04a0 --- /dev/null +++ b/tests/check_self_pipe.c @@ -0,0 +1,197 @@ +#include +#include +#include + +#include +#include + +#include "self_pipe.h" + +START_TEST( test_opens_pipe ) +{ + struct self_pipe* sig; + char buf[] = " "; + + sig = self_pipe_create(); + + write( sig->write_fd, "1", 1 ); + read( sig->read_fd, buf, 1 ); + + fail_unless( buf[0] == '1', "Pipe does not seem to be open;" ); + self_pipe_destroy( sig ); +} +END_TEST + + +void * signal_thread( void * thing ) +{ + struct self_pipe *sig = (struct self_pipe *)thing; + usleep( 100000 ); + self_pipe_signal( sig ); + return NULL; +} + +pthread_t start_signal_thread( struct self_pipe *sig ) +{ + pthread_attr_t attr; + pthread_t thread_id; + + pthread_attr_init( &attr ); + pthread_create( &thread_id, &attr, signal_thread, sig ); + pthread_attr_destroy( &attr ); + + return thread_id; +} + + +START_TEST( test_signals ) +{ + struct self_pipe* sig; + fd_set fds; + pthread_t signal_thread_id; + + sig = self_pipe_create(); + + FD_ZERO( &fds ); + self_pipe_fd_set( sig, &fds ); + + signal_thread_id = start_signal_thread( sig ); + if ( select( FD_SETSIZE, &fds, NULL, NULL, NULL ) == -1 ) { + fail( strerror(errno) ); + } + self_pipe_signal_clear( sig ); + + fail_unless( self_pipe_fd_isset( sig, &fds ), "Signalled pipe was not FD_ISSET." ); + pthread_join( signal_thread_id, NULL ); + + self_pipe_destroy( sig ); +} +END_TEST + + +START_TEST( test_destroy_closes_read_pipe ) +{ + struct self_pipe* sig; + ssize_t read_len; + int orig_read_fd; + + sig = self_pipe_create(); + orig_read_fd = sig->read_fd; + self_pipe_destroy( sig ); + + while( (read_len = read( orig_read_fd, "", 0 )) == -1 && errno == EINTR ); + + switch( read_len ) { + case 0: + fail("The read fd wasn't closed." ); + break; + case -1: + switch(errno) { + case EBADF: + /* This is what we want */ + break; + case EAGAIN: + fail( "The read fd wasn't closed." ); + break; + default: + fail( strerror( errno ) ); + break; + } + break; + default: + fail( "The read fd wasn't closed, and had data in it." ); + break; + } +} +END_TEST + + +START_TEST( test_destroy_closes_write_pipe ) +{ + struct self_pipe * sig; + ssize_t write_len; + int orig_write_fd; + + sig = self_pipe_create(); + orig_write_fd = sig->write_fd; + self_pipe_destroy( sig ); + + while ( ( write_len = write( orig_write_fd, "", 0 ) ) == -1 && errno == EINTR ); + + switch( write_len ) { + case 0: + fail( "The write fd wasn't closed." ); + break; + case -1: + switch( errno ) { + case EPIPE: + case EBADF: + /* This is what we want */ + break; + case EAGAIN: + fail("The write fd wasn't closed." ); + break; + default: + fail( strerror( errno ) ); + break; + } + break; + default: + /* To get here, the write(_,_,0) would have to + * write some bytes. + */ + fail( "The write fd wasn't closed, and something REALLY WEIRD is going on." ); + break; + } + +} +END_TEST + + +START_TEST( test_signal_after_destroy_fails ) +{ + struct self_pipe* sig; + + sig = self_pipe_create(); + self_pipe_destroy( sig ); + + fail_unless( self_pipe_signal( sig ) == 0, "Signaling a closed self_pipe didn't return 0." ); +} +END_TEST + + +Suite *self_pipe_suite() +{ + Suite *s = suite_create("self_pipe"); + + TCase *tc_create = tcase_create("create"); + TCase *tc_signal = tcase_create("signal"); + TCase *tc_destroy = tcase_create("destroy"); + + tcase_add_test(tc_create, test_opens_pipe); + tcase_add_test(tc_signal, test_signals ); + tcase_add_test(tc_destroy, test_destroy_closes_read_pipe ); + tcase_add_test(tc_destroy, test_destroy_closes_write_pipe ); + /* We don't test that destroy free()'s the self_pipe pointer because + * that'll be caught by valgrind. + */ + + suite_add_tcase(s, tc_create); + suite_add_tcase(s, tc_signal); + suite_add_tcase(s, tc_destroy); + + return s; +} + +int main(void) +{ + int number_failed; + + Suite *s = self_pipe_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? 0 : 1; +} +