#define _POSIX_C_SOURCE 200809L #include /* exit */ #include /* assert */ #include /* read, write, fork, close, alarm */ #include /* err, warn, warnx */ /* Naprogramujte proceduru ‹clone_stream›, která dostane jako * parametry 3 popisovače připojených proudových socketů. Jejím * úkolem bude přeposílat data příchozí na první z nich ‹in_fd› do * dvou dalších (‹out1_fd›, ‹out2_fd›). * * Klonování bude probíhat synchronně – nestíhá-li protistrana na * některém výstupním popisovači (‹out1_fd›, ‹out2_fd›) data * dostatečně rychle zpracovávat, ‹clone_stream› nebude další data * (z ‹in_fd›) načítat, dokud se situace nezlepší (a tím případně * regulovat produkci dat na zdroji). * * Procedura ‹clone_stream› skončí, je-li ukončeno spojení na * ‹in_fd›, nebo na obou výstupech (jinak řečeno, přeposílání bude * pokračovat tak dlouho, dokud je připojen odesílatel a alespoň * jeden z příjemců). Návratová hodnota nechť je 0, došlo-li ke * korektnímu ukončení spojení, nebo -1 při fatální chybě. */ int clone_stream( int in_fd, int out1_fd, int out2_fd ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* socketpair */ #include /* waitpid */ #include /* signal, SIGPIPE, SIG_IGN */ #include /* strcmp, strlen */ static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "wait" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } pid_t spawn_cloner( int *fd_in, int *fd_1, int *fd_2 ) { int sock_in[ 2 ]; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, sock_in ) == -1 ) err( 1, "input socketpair" ); int sock_1[ 2 ]; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, sock_1 ) == -1 ) err( 1, "output socketpair 1" ); int sock_2[ 2 ]; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, sock_2 ) == -1 ) err( 1, "output socketpair 2" ); pid_t pid = fork(); if ( pid == -1 ) err( 1, "fork" ); if ( pid > 0 ) { close_or_warn( sock_in[ 1 ], "child's input socket in parent" ); close_or_warn( sock_1[ 1 ], "child's output socket 1 in parent" ); close_or_warn( sock_2[ 1 ], "child's output socket 2 in parent" ); *fd_in = sock_in[ 0 ]; *fd_1 = sock_1[ 0 ]; *fd_2 = sock_2[ 0 ]; return pid; } /* Při pokusu o zápis do proudového soketu, jehož druhý konec už * je uzavřen pro čtení, program obdrží signál SIGPIPE. Ten ve * výchozím stavu celý program ukončí. To není vhodné, chceme-li * umět poznat, že druhá strana již zapisovaná data nečte, a * nějak na situaci zareagovat. Následujícím voláním proto * necháme signál ignorovat. Při pokusu o zápis do již nečteného * soketu pak ‹write› vrátí -1 a nastaví ‹errno› na ‹EPIPE›. */ if ( signal( SIGPIPE, SIG_IGN ) == SIG_ERR ) err( 1, "signal" ); alarm( 1 ); close_or_warn( sock_in[ 0 ], "parent's input socket in child" ); close_or_warn( sock_1[ 0 ], "parent's output socket 1 in child" ); close_or_warn( sock_2[ 0 ], "parent's output socket 2 in child" ); int rv = clone_stream( sock_in[ 1 ], sock_1[ 1 ], sock_2[ 1 ] ); close_or_warn( sock_in[ 1 ], "child's input socket" ); close_or_warn( sock_1[ 1 ], "child's output socket 1" ); close_or_warn( sock_2[ 1 ], "child's output socket 2" ); exit( rv ); } static int push( int fd, const char *msg ) { if ( write( fd, msg, strlen( msg ) ) == -1 ) return warn( "push write" ), 0; return 1; } static int pull( int fd, const char *msg ) { char buf[ 256 ]; int len = strlen( msg ); int read_total = 0; int bytes_read; while ( read_total < len && ( bytes_read = read( fd, buf + read_total, sizeof buf - read_total - 1 ) ) > 0 ) { read_total += bytes_read; } if ( bytes_read == -1 ) return warn( "pull read" ), 0; buf[ read_total ] = '\0'; if ( strcmp( msg, buf ) != 0 ) return warnx( "pull unexpected message: %s", buf ), 0; return 1; } static int transfer( int fd_in, int fd_out1, int fd_out2, const char *msg ) { return push( fd_in, msg ) && pull( fd_out2, msg ) && pull( fd_out1, msg ); } int main( void ) { int fd_in, fd_1, fd_2; pid_t pid = spawn_cloner( &fd_in, &fd_1, &fd_2 ); assert( transfer( fd_in, fd_1, fd_2, "hello" ) ); close_or_warn( fd_in, "parent's input socket" ); close_or_warn( fd_1, "parent's output socket 1" ); close_or_warn( fd_2, "parent's output socket 2" ); assert( reap( pid ) == 0 ); pid = spawn_cloner( &fd_in, &fd_1, &fd_2 ); assert( transfer( fd_in, fd_1, fd_2, "first closes first" ) ); close_or_warn( fd_1, "parent's output socket 1" ); assert( push( fd_in, "only second gets this" ) ); close_or_warn( fd_in, "parent's input socket" ); assert( pull( fd_2, "only second gets this" ) ); close_or_warn( fd_2, "parent's output socket 2" ); assert( reap( pid ) == 0 ); pid = spawn_cloner( &fd_in, &fd_1, &fd_2 ); assert( transfer( fd_in, fd_1, fd_2, "second closes first" ) ); close_or_warn( fd_2, "parent's output socket 2" ); assert( push( fd_in, "only first gets this" ) ); assert( pull( fd_1, "only first gets this" ) ); close_or_warn( fd_1, "parent's output socket 1" ); close_or_warn( fd_in, "parent's input socket" ); assert( reap( pid ) == 0 ); return 0; }