#define _POSIX_C_SOURCE 200809L #include /* exit */ #include /* assert */ #include /* read, write, fork, close */ #include /* Vaším úkolem je naprogramovat proceduru ‹log_stream›, která bude * přeposílat data z jednoho připojeného proudového socketu na * druhý, a přitom bude v souboru udržovat záznam o takto * přeposlaných datech. Parametry: * * • ‹in_fd› – popisovač, ze kterého bude data číst, * • ‹out_fd› – popisovač, do kterého bude data přeposílat, * • ‹log_fd› – popisovač souboru, ve kterém bude udržovat záznamy, * • ‹limit› – limit na počet bajtů uložených v souboru ‹log_fd›. * * Dojde-li během přenosu kapacita logovacího souboru, tento bude * zkrácen na polovinu limitu smazáním nejstarších dat. Návratová * hodnota ‹-1› indikuje chybu, jinak procedura vrátí počet bajtů, * které byly přeposlány, ale ze souboru ‹log_fd› byly pro * nedostatek místa smazány. * * Poznámka: Vhodnou volbou velikosti čtení z popisovače ‹in_fd› se * může řešení značně zjednodušit (zejména ji můžete přizpůsobit * aktuálně volnému místu v logovacím souboru). */ int log_stream( int in_fd, int out_fd, int log_fd, int limit ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* open */ #include /* socketpair */ #include /* waitpid */ #include /* strcmp, strlen */ #include /* nanosleep */ 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; } static int min( int a, int b ) { return a < b ? a : b; } static pid_t fork_sender( const char *msg, int in_fd[ 2 ], int out_fd[ 2 ], int send_size ) { pid_t pid = fork(); if ( pid == -1 ) err( 1, "fork sender" ); if ( pid > 0 ) return pid; close_or_warn( in_fd[ 1 ], "logger's in socket in sender" ); close_or_warn( out_fd[ 0 ], "logger's out socket in sender" ); close_or_warn( out_fd[ 1 ], "receiver's socket in sender" ); alarm( 1 ); int msg_len = strlen( msg ); if ( send_size == 0 ) send_size = msg_len; int sent_total = 0; int sent; while ( msg_len - sent_total > 0 && ( sent = write( in_fd[ 0 ], msg + sent_total, min( send_size, msg_len - sent_total ) ) ) > 0 ) { sent_total += sent; /* Počkat 5 ms v naději, že si druhá strana stihne vyzvednout data, než * se jich ve vyrovnávací paměti socketu nahromadí víc. */ struct timespec ts = { .tv_sec = 0, .tv_nsec = 5000000 }; if ( nanosleep( &ts, NULL ) == -1 ) err( 1, "nanosleep" ); } if ( sent == -1 ) err( 1, "sender: write" ); close_or_warn( in_fd[ 0 ], "sender's socket" ); exit( 0 ); } static pid_t fork_receiver( const char *msg, int in_fd[ 2 ], int out_fd[ 2 ] ) { pid_t pid = fork(); if ( pid == -1 ) err( 1, "fork receiver" ); if ( pid > 0 ) return pid; alarm( 1 ); close_or_warn( in_fd[ 0 ], "sender's socket in receiver" ); close_or_warn( in_fd[ 1 ], "logger's in socket in receiver" ); close_or_warn( out_fd[ 0 ], "logger's out socket in receiver" ); char buf[ 256 ]; int read_total = 0; int bytes_read; while ( ( bytes_read = read( out_fd[ 1 ], buf + read_total, sizeof buf - read_total - 1 ) ) > 0 ) { read_total += bytes_read; } if ( bytes_read == -1 ) err( 1, "receiver: read" ); buf[ read_total ] = '\0'; if ( strcmp( msg, buf ) != 0 ) errx( 1, "received unexpected message: %s", buf ); close_or_warn( out_fd[ 1 ], "receiver's socket" ); exit( 0 ); } static int run( const char *msg, int limit, int send_size ) { int in_fd[ 2 ]; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, in_fd ) == -1 ) err( 1, "input socketpair" ); int out_fd[ 2 ]; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, out_fd ) == -1 ) err( 1, "output socketpair" ); pid_t sender_pid = fork_sender( msg, in_fd, out_fd, send_size ); pid_t receiver_pid = fork_receiver( msg, in_fd, out_fd ); close_or_warn( in_fd[ 0 ], "sender's socket in logger" ); close_or_warn( out_fd[ 1 ], "receiver's socket in logger" ); int log_fd = open( "zt.r5_log", O_RDWR | O_CREAT | O_TRUNC, 0600 ); if ( log_fd == -1 ) err( 1, "open log" ); int rv = log_stream( in_fd[ 1 ], out_fd[ 0 ], log_fd, limit ); close_or_warn( log_fd, "log" ); close_or_warn( in_fd[ 1 ], "logger's input socket" ); close_or_warn( out_fd[ 0 ], "logger's output socket" ); if ( reap( sender_pid ) != 0 || reap( receiver_pid ) != 0 ) rv = -2; return rv; } static int log_contains( const char *msg ) { int log_fd = open( "zt.r5_log", O_RDONLY, 0600 ); if ( log_fd == -1 ) err( 1, "open log" ); char buf[ 256 ]; int bytes_read = read( log_fd, buf, sizeof buf - 1 ); if ( bytes_read == -1 ) err( 1, "read log" ); close_or_warn( log_fd, "log" ); buf[ bytes_read ] = '\0'; return strcmp( msg, buf ) == 0; } int main( void ) { for ( int sndbuf = 0; sndbuf < 10; ++sndbuf ) { assert( 0 == run( "hello", 6, sndbuf ) ); assert( log_contains( "hello" ) ); assert( 3 == run( "hello!!", 6, sndbuf ) ); assert( log_contains( "lo!!" ) ); assert( 6 == run( "hello world", 6, sndbuf ) ); assert( log_contains( "world" ) ); assert( 9 == run( "hello everyone!", 6, sndbuf ) ); assert( log_contains( "ryone!" ) ); } if ( unlink( "zt.r5_log" ) == -1 ) err( 1, "unlink log" ); return 0; }