#include "esd-server.h" #include #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif #ifdef HAVE_SYS_FILIO_H # include #endif #ifdef USE_LIBWRAP #include #include int allow_severity = LOG_INFO; int deny_severity = LOG_WARNING; #endif /*******************************************************************/ /* globals */ extern int esd_use_tcpip; #if defined (ENABLE_IPV6) extern int esd_use_ipv6; #endif /* the list of the currently connected clients */ esd_client_t *esd_clients_list; static int write_wait = 0; /*******************************************************************/ /* prototypes */ void dump_clients(void); void free_client( esd_client_t *client ); /*******************************************************************/ /* for debugging purposes, dump the list of the clients and data */ void dump_clients(void) { long addr; short port; esd_client_t *clients = esd_clients_list; if ( !esdbg_trace ) return; while ( clients != NULL ) { #if defined (ENABLE_IPV6) if ( esd_use_ipv6 ) { char addrbuf[INET6_ADDRSTRLEN]; port = ntohs( clients->source6.sin6_port ); #ifdef HAVE_INET_NTOP addrbuf[0] = '\0'; if ( inet_ntop( AF_INET6, &(clients->source6.sin6_addr), addrbuf, sizeof(addrbuf) )) printf( "Client from: %s/%d [%p]\n", addrbuf, port, clients ); #endif } else #endif { port = ntohs( clients->source.sin_port ); addr = ntohl( clients->source.sin_addr.s_addr ); printf( "(%02d) client from: %03u.%03u.%03u.%03u:%05d [%p]\n", clients->fd, (unsigned int) addr >> 24, (unsigned int) (addr >> 16) % 256, (unsigned int) (addr >> 8) % 256, (unsigned int) addr % 256, port, clients ); clients = clients->next; } } return; } /*******************************************************************/ /* deallocate memory for the client */ void free_client( esd_client_t *client ) { /* free the client memory */ free( client ); return; } /*******************************************************************/ /* add a complete new client into the list of clients at head */ void add_new_client( esd_client_t *new_client ) { /* printf ( "adding client 0x%08x\n", new_client ); */ new_client->next = esd_clients_list; esd_clients_list = new_client; return; } /*******************************************************************/ /* erase a client from the client list */ void erase_client( esd_client_t *client ) { esd_client_t *previous = NULL; esd_client_t *current = esd_clients_list; /* iterate until we hit a NULL */ while ( current != NULL ) { /* see if we hit the target client */ if ( current == client ) { if( previous != NULL ){ /* we are deleting in the middle of the list */ previous->next = current->next; } else { /* we are deleting the head of the list */ esd_clients_list = current->next; } ESDBG_TRACE( printf ( "(%02d) closing client connection\n", client->fd ); ); close( client->fd ); free_client( client ); return; } /* iterate through the list */ previous = current; current = current->next; } /* hmm, we didn't find the desired client, just get on with life */ ESDBG_TRACE( printf( "(%02d) client not found\n", client->fd ); ); return; } /*******************************************************************/ /* checks for new connections at listener - zero when done */ int get_new_clients( int listen ) { int fd, nbl; struct sockaddr_in incoming; #if defined (ENABLE_IPV6) struct sockaddr_in6 incoming6; size_t size_in6 = sizeof(struct sockaddr_in6); #endif size_t size_in = sizeof(struct sockaddr_in); esd_client_t *new_client = NULL; short port; /* see who awakened us */ do { #if defined (ENABLE_IPV6) if ( esd_use_ipv6 ) { char addrbuf[INET6_ADDRSTRLEN]; fd = accept( listen,(struct sockaddr *)&incoming6, (socklen_t *) &size_in6 ); if ( fd < 0 ) goto again; port = ntohs( incoming6.sin6_port ); #ifdef HAVE_INET_NTOP addrbuf[0] = '\0'; if ( inet_ntop( AF_INET6, &(incoming6.sin6_addr), addrbuf, sizeof(addrbuf) )) ESDBG_TRACE( printf( "client from :%s/%d\n", addrbuf, port ); ); #endif } else #endif { fd = accept( listen, (struct sockaddr*) &incoming, (socklen_t *) &size_in ); if ( fd < 0 ) goto again; ESDBG_TRACE( if (esd_use_tcpip) { port = ntohs( incoming.sin_port ); addr = ntohl( incoming.sin_addr.s_addr ); printf( "(%02d) new client from: %03u.%03u.%03u.%03u:%05d\n", fd, (unsigned int) addr >> 24, (unsigned int) (addr >> 16) % 256, (unsigned int) (addr >> 8) % 256, (unsigned int) addr % 256, port ); } ); } #ifdef USE_LIBWRAP if (esd_use_tcpip) { struct request_info req; request_init( &req, RQ_DAEMON, "esound", RQ_FILE, fd, NULL ); fromhost( &req ); if ( !hosts_access( &req )) { ESDBG_TRACE( printf( "connection from %s refused by tcp_wrappers\n", eval_client( &req ) ); ); close( fd ); continue; } } #endif ESDBG_COMMS( printf( "================================\n" ); ); /* make sure we have the memory to save the client... */ new_client = (esd_client_t*) malloc( sizeof(esd_client_t) ); if ( new_client == NULL ) { close( fd ); return -1; } /* It appears that not all systems construct the new socket in * a blocking mode, if the listening socket is non-blocking, so * let's set that here... */ nbl = 0; if ( ioctl( fd, FIONBIO, &nbl ) < 0 ) { ESDBG_TRACE( printf( "(%02d) couldn't turn on blocking for client\n", fd ); ); close( fd ); free( new_client ); return -1; } /* Reduce buffers on sockets to the minimum needed */ esd_set_socket_buffers( fd, ESD_BITS16, 44100, esd_audio_rate ); /* fill in the new_client structure - sockaddr = works!? */ new_client->next = NULL; new_client->state = ESD_NEEDS_REQDATA; new_client->request = ESD_PROTO_CONNECT; new_client->fd = fd; #if defined (ENABLE_IPV6) if ( esd_use_ipv6 ) new_client->source6 = incoming6; else #endif new_client->source = incoming; new_client->proto_data_length = 0; add_new_client( new_client ); again: if( fd < 0 ) ESDBG_TRACE ( printf("Error encountered while accepting connection\n")); } while ( fd > 0 ); return 0; } void esd_comm_loop( int listen_socket, void *output_buffer, int esd_terminate ) { fd_set rd_fds; struct timeval timeout; struct timeval *timeout_ptr = NULL; esd_client_t *client; int max_fd = listen_socket, ready; int first = 1; int is_paused_here = 0; int length = 0; while ( 1 ) { FD_ZERO( &rd_fds ); FD_SET( listen_socket, &rd_fds ); if( esd_pending_driver_reconnect ) { esd_pending_driver_reconnect = 0; esd_audio_close(); usleep(100); if( esd_audio_open() < 0 ) fprintf( stderr, "could not reopen the audio device\n" ); } if ((esd_clients_list == NULL) && (!first) && (esd_terminate) && (esd_autostandby_secs<0 || esd_on_autostandby)) { clean_exit(0); exit(0); } for( client = esd_clients_list ; client ; client = client->next) { if ( client->state == ESD_STREAMING_DATA && client->request == ESD_PROTO_STREAM_MON ) continue; FD_SET( client->fd, &rd_fds ); if ( client->fd > max_fd ) max_fd = client->fd; } /* if we're doing something useful, make sure we return immediately */ if ( esd_recorder_list || esd_playing_samples ) { timeout.tv_sec = 0; timeout.tv_usec = 0; timeout_ptr = &timeout; } else { if ( is_paused_here && (esd_autostandby_secs<0 || esd_on_standby)) { ESDBG_TRACE( printf( "paused, awaiting instructions.\n" ); ); timeout_ptr = NULL; } else { if (is_paused_here && esd_autostandby_secs>=0) { timeout.tv_sec = (esd_last_activity+esd_autostandby_secs+1)-time(NULL); if (timeout.tv_sec < 0 || timeout.tv_sec > esd_autostandby_secs+1) timeout.tv_sec = 0; timeout.tv_usec = 0; timeout_ptr = &timeout; } else { if (!write_wait) { int samples; samples = ((esd_audio_format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1; timeout.tv_sec = 0; /* funky math to make sure a long can hold it all, calulate in ms */ timeout.tv_usec = ((long) samples * 1000L / (long) esd_audio_rate)+1; timeout.tv_usec *= 1000; /* convert to microseconds */ timeout_ptr = &timeout; } else timeout_ptr = NULL; } } } ready = select( max_fd+1, &rd_fds, NULL, NULL, timeout_ptr ); ESDBG_COMMS( printf( "paused=%d, samples=%d, auto=%d, standby=%d, record=%d, ready=%d\n", is_paused_here, esd_playing_samples, esd_autostandby_secs, esd_on_standby, (esd_recorder_list != 0), ready ); ); if ( ready < 0 ) { if ( errno == EINTR && timeout_ptr == NULL && write_wait == 0 ) { ready = 1; } else { if ( errno == EINTR || errno == EAGAIN ) continue; perror("select"); exit(1); } } else if ( ready == 0 ) { if ( !is_paused_here && !esd_playing_samples && !esd_recorder_list ) { ESDBG_TRACE( printf( "doing nothing, pausing server.\n" ); ); esd_audio_flush(); if (!first) esd_audio_pause(); esd_last_activity = time( NULL ); is_paused_here = 1; } if ( is_paused_here && esd_autostandby_secs >= 0 && ( time(NULL) > esd_last_activity + esd_autostandby_secs ) ) { ESDBG_TRACE( printf( "bored, going to standby mode.\n" ); ); esd_server_standby(); esd_on_autostandby = 1; } if (!esd_recorder_list && is_paused_here) continue; } is_paused_here = 0; if ( FD_ISSET(listen_socket, &rd_fds ) ) { get_new_clients( listen_socket ); } if ( ready || esd_playing_samples ) { /* check for new protocol requests */ poll_client_requests(); first = 0; /* mix new requests, and output to device */ refresh_mix_funcs(); /* TODO: set a flag to cue when to do this */ length = mix_players( output_buffer, esd_buf_size_octets ); /* awaken if on autostandby and doing anything */ if ( esd_on_autostandby && length && !esd_forced_standby ) { ESDBG_TRACE( printf( "stuff to play, waking up.\n" ); ); if ( !esd_server_resume()) { usleep(100); } } /* we handle this even when length == 0 because a filter could have * closed, and we don't want to eat the processor if one did.. */ if ( esd_filter_list && !esd_on_standby ) { length = filter_write( output_buffer, length, esd_audio_format, esd_audio_rate ); } if ( length > 0 && !write_wait) { if ( !esd_on_standby ) { int nbytes; /* standby check goes in here, so esd will eat sound data */ /* TODO: eat a round of data with a better algorithm */ /* this will cause guaranteed timing issues */ /* TODO: on monitor, why isn't this a buffer of zeroes? */ nbytes = esd_audio_write( output_buffer, length ); #if 0 val.tv_sec = 0; val.tv_usec = ((nbytes / esd_sample_size / (((esd_audio_format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1)) * 1000) / esd_audio_rate; val.tv_usec *= 1000000; #endif esd_last_activity = time( NULL ); } /* if someone's monitoring the sound stream, send them data */ /* mix_players, above, forces buffer to zero if no players */ /* this clears out any leftovers from recording, below */ if ( esd_monitor_list && !esd_on_standby && length ) { monitor_write( output_buffer, length ); } } } /* if someone's recording from the audio device, send them data */ if ( esd_recorder_list && !esd_on_standby ) { length = esd_audio_read( output_buffer, esd_buf_size_octets ); if ( length ) { length = recorder_write( output_buffer, length ); esd_last_activity = time( NULL ); } } } }