#define _POSIX_C_SOURCE 200809L #include /* read, write, pipe */ #include /* assert */ #include /* memcmp */ #include /* malloc */ #include /* err */ #include /* ‡ Uvažme situaci, kdy chceme stejná data zapisovat do několika * komunikačních kanálů, které ale nejsou tato data schopna * zpracovávat stejně rychle. Zároveň nechceme program blokovat * v situaci, kdy některý komunikační kanál není schopen data ihned * přijmout. Navrhněte sadu podprogramů, která bude tento problém * řešit: * * • ‹tee_init› obdrží pole popisovačů, do kterých si přejeme data * kopírovat, a vrátí ukazatel ‹handle› (nebo nulový ukazatel * v případě selhání) – popisovače jsou již nastavené * v neblokujícím režimu, * • ‹tee_write› obdrží ukazatel ‹handle›, ukazatel na data a * velikost dat, které si přejeme rozeslat, * • ‹tee_fini› zapíše všechna zbývající data a uvolní veškeré * zdroje přidružené k předanému ukazateli ‹handle›. * * Podprogram ‹tee_write› nebude za žádných okolností blokovat. * Zároveň zabezpečí, že veškerá data, která odeslat lze, budou * odeslána ihned, a že data, která už byla odeslána do všech * kanálů, nebudou zabírat paměť. Podprogram ‹tee_write› lze volat * s nulovou velikostí – v takovém případě pouze provede případné * odložené zápisy. Návratová hodnota ‹tee_write› je: * * • 0 – veškerá data byla odeslána všem příjemcům, * • kladné číslo – počet popisovačů, které jsou „pozadu“, * • -1 – nastala chyba, která znemožnila zpracování předaných dat * (data lze bezpečně do ‹tee_write› předat znovu, aniž by * hrozilo dvojité odeslání). * * Nastane-li chyba při zápisu na některý popisovač, ‹tee_write› * tento popisovač přeskočí a data se pokusí při dalším volání opět * odeslat. * * Konečně ‹tee_fini› dokončí veškeré zápisy. Dojde-li při některém * z nich k chybě, vrátí ‹-1›, ale až potom, co dokončí všechny * zápisy, které provést lze, a uvolní zdroje spojené s ukazatelem * ‹handle› (včetně uzavření všech přidružených popisovačů). * * Očekává se, že ‹tee_fini› zavře všechny popisovače předané * ‹tee_ini›. */ void *tee_init( int fd_count, int *fds ); int tee_write( void *handle, const char *data, int nbytes ); int tee_fini( void *handle ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* PIPE_BUF */ #include /* fcntl, O_NONBLOCK */ static void fill( int fd, int byte ) { char buffer[ PIPE_BUF ]; memset( buffer, byte, PIPE_BUF ); int wrote = write( fd, buffer, PIPE_BUF ); if ( wrote == -1 && errno != EAGAIN && errno != EWOULDBLOCK ) err( 1, "filling up a pipe" ); if ( wrote == PIPE_BUF ) return fill( fd, byte ); } static void drain( int fd, int byte ) { char buffer[ PIPE_BUF ]; int readb = read( fd, buffer, PIPE_BUF ); if ( readb == -1 && errno != EAGAIN && errno != EWOULDBLOCK ) err( 1, "draining pipe" ); if ( readb <= 0 ) return; for ( int i = 0; i < readb; ++i ) assert( buffer[ i ] == byte ); drain( fd, byte ); } static int readbuf( int fd, char *buffer, int buflen ) { int bytes, total = 0; while ( ( bytes = read( fd, buffer + total, buflen - total ) ) > 0 && total < buflen ) total += bytes; return bytes == -1 ? -1 : total; } static void close_or_warn( int fd, const char *what, int v ) { if ( close( fd ) == -1 ) warn( what, v ); } int main( void ) { char buffer[ 256 ]; int fds_read[ 3 ], fds_write[ 3 ]; for ( int i = 0; i < 3; i++ ) { int p[ 2 ]; if ( pipe( p ) != 0 ) err( 1, "unable to open pipes" ); if ( fcntl( p[ 0 ], F_SETFL, O_NONBLOCK ) == -1 ) err( 1, "setting O_NONBLOCK for reading" ); if ( fcntl( p[ 1 ], F_SETFL, O_NONBLOCK ) == -1 ) err( 1, "setting O_NONBLOCK for writing" ); fds_read[ i ] = p[ 0 ]; fds_write[ i ] = p[ 1 ]; } void *h = tee_init( 3, fds_write ); assert( tee_write( h, "hello", 5 ) == 0 ); for ( int i = 0; i < 3; i++ ) { assert( readbuf( fds_read[ i ], buffer, 5 ) == 5 ); assert( memcmp( buffer, "hello", 5 ) == 0 ); } fill( fds_write[ 2 ], 0 ); assert( tee_write( h, "world", 5 ) == 1 ); drain( fds_read[ 2 ], 0 ); assert( tee_write( h, "lorem", 5 ) == 0 ); for ( int i = 0; i < 3; i++ ) { assert( readbuf( fds_read[ i ], buffer, 10 ) == 10 ); assert( memcmp( buffer, "worldlorem", 10 ) == 0 ); } fill( fds_write[ 1 ], 0 ); fill( fds_write[ 2 ], 0 ); assert( tee_write( h, "ipsum", 5 ) == 2 ); drain( fds_read[ 2 ], 0 ); assert( tee_write( h, "dolor", 5 ) == 1 ); drain( fds_read[ 1 ], 0 ); assert( tee_write( h, "sit", 3 ) == 0 ); for ( int i = 0; i < 3; i++ ) { assert( readbuf( fds_read[ i ], buffer, 13 ) == 13 ); assert( memcmp( buffer, "ipsumdolorsit", 13 ) == 0 ); } for ( int i = 0; i < 3; i++ ) fill( fds_write[ i ], 0 ); assert( tee_write( h, "amet", 4 ) == 3 ); drain( fds_read[ 0 ], 0 ); assert( tee_write( h, "consectetur", 11 ) == 2 ); assert( readbuf( fds_read[ 0 ], buffer, 15 ) == 15 ); assert( memcmp( buffer, "ametconsectetur", 15 ) == 0 ); drain( fds_read[ 1 ], 0 ); assert( tee_write( h, "adipiscing", 10 ) == 1 ); assert( readbuf( fds_read[ 0 ], buffer, 10 ) == 10 ); assert( memcmp( buffer, "adipiscing", 10 ) == 0 ); assert( readbuf( fds_read[ 1 ], buffer, 25 ) == 25 ); assert( memcmp( buffer, "ametconsecteturadipiscing", 25 ) == 0 ); drain( fds_read[ 2 ], 0 ); assert( tee_write( h, "elit", 4 ) == 0 ); assert( readbuf( fds_read[ 0 ], buffer, 4 ) == 4 ); assert( memcmp( buffer, "elit", 4 ) == 0 ); assert( readbuf( fds_read[ 1 ], buffer, 4 ) == 4 ); assert( memcmp( buffer, "elit", 4 ) == 0 ); assert( readbuf( fds_read[ 2 ], buffer, 29 ) == 29 ); assert( memcmp( buffer, "ametconsecteturadipiscingelit", 29 ) == 0 ); for ( int i = 0; i < 3; i++ ) fill( fds_write[ i ], 0 ); assert( tee_write( h, "sed", 3 ) == 3 ); for ( int i = 0; i < 3; i++ ) drain( fds_read[ i ], 0 ); assert( tee_fini( h ) == 0 ); for ( int i = 0; i < 3; i++ ) { assert( readbuf( fds_read[ i ], buffer, 3 ) == 3 ); assert( memcmp( buffer, "sed", 3 ) == 0 ); } for ( int i = 0; i < 3; ++i ) { close_or_warn( fds_read[ i ], "closing read end %d", i ); close_or_warn( fds_write[ i ], "closing write end %d", i ); } return 0; }