#define _POSIX_C_SOURCE 200809L #include /* htonl, ntohl */ #include /* assert */ #include /* uint32_t */ #include /* exit, NULL */ #include /* strlen, strcmp */ #include /* read, lseek, close, unlink, fork, alarm */ #include /* open, O_RDONLY, O_WRONLY, O_CREAT, O_TRUNC */ #include /* socketpair */ #include /* socketpair, AF_UNIX */ #include /* errno */ #include /* err, warn, errx */ #include /* kill, SIGTERM */ /* V této přípravě je Vaším úkolem implementovat klient, který bude * synchronizovat data ze serveru, a to tak, že přenese vždy jen ta * data, která ještě nemá uložena lokálně. * * Server má uloženu sekvenci bajtů, která se může postupně * prodlužovat, nemůže se ale ani zkrátit, ani se již uložené bajty * nemohou měnit. * * Protokol funguje takto: * * 1. klient odešle dvojici čísel ‹n, k› (každé zakódované do 4 * bajtů, nejvýznamnější bajt první – tzn. v pořadí big endian), * 2. server obratem odešle své uložené bajty počínaje tím na * indexu ‹n›, přitom jich ale odešle nejvýše ‹k› (jediná * situace, kdy odešle méně než ‹k› bajtů je, když je na serveru * uloženo méně než ‹n + k› bajtů). * * Hodnota ‹n = 0xffff'ffff› je vyhrazená pro nahrávání dat na * server (klient v tomto příkladu tuto hodnotu používat nebude). * * Naprogramujte proceduru ‹news_update›, která bude mít tyto dva * parametry: * * • ‹state_fd› – popisovač souboru, který obsahuje již známá data, * a do kterého budou nová data připsána (tzn. po ukončení * ‹news_update› bude soubor obsahovat přesnou kopii dat ze * serveru), * • ‹sock_fd› – popisovač datagramového socketu s maximální * velikostí datagramu nejvýše 1024 bajtů, * * O popisovači ‹state_fd› nepředpokládejte nic jiného, než že se * jedná o obyčejný soubor otevřený pro zápis (zejména může ukazovat * na libovolnou pozici). * * Návratová hodnota 0 indikuje, že soubor ‹state_fd› byl úspěšně * doplněn. Nastane-li libovolná chyba, procedura vrátí hodnotu -1 a * obsah souboru ‹state› nezmění. * * «Pozor!» Při práci s datagramy nulové délky je potřeba dbát * opatrnosti protože systémové volání ‹read› s nulovou délkou čtení * neprovede žádnou akci. */ int news_update( int state_fd, int sock_fd ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* wait */ #include /* pid_t */ static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static void unlink_if_exists( const char* file ) { if ( unlink( file ) == -1 && errno != ENOENT ) err( 2, "unlinking %s", file ); } static pid_t fork_server( int fds[ 2 ], int file_fd, const char *msg ) { int server_fd = fds[ 0 ], client_fd = fds[ 1 ]; pid_t child_pid = fork(); if ( child_pid == -1 ) err( 2, "fork" ); if ( child_pid > 0 ) return child_pid; close_or_warn( client_fd, "client socket" ); close_or_warn( file_fd, "zt.p4_state" ); // Terminate the server after 5 seconds in case it doesn't exit naturally alarm( 5 ); int msg_len = strlen( msg ); while ( 1 ) { uint32_t buff[ 2 ]; ssize_t recvd = recv( server_fd, buff, sizeof( buff ), 0 ); if ( recvd == - 1 ) err( 2, "server receiving header" ); if ( recvd != 8 ) errx( 1, "server received header of wrong size %zd", recvd ); int size = ntohl( buff[ 1 ] ); int off = ntohl( buff[ 0 ] ); int to_send ; if ( off > msg_len ) to_send = 0; else if ( off + size <= msg_len ) to_send = size; else to_send = msg_len - off; if ( send( server_fd, msg + off, to_send, 0 ) == -1 ) err( 2, "server writing to fd" ); } close_or_warn( server_fd, "server socket" ); exit( 0 ); } static int kill_and_reap( pid_t pid ) { if ( kill( pid, SIGTERM ) == -1 ) err( 2, "kill" ); int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "wait" ); if ( WIFSIGNALED( status ) && WTERMSIG( status ) == SIGTERM ) return 0; if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } static int test_file_contains( const char *contents ) { int fd = open( "zt.p4_state", O_RDONLY ); if ( fd == -1 ) err( 1, "opening %s for reading", "zt.p4_state" ); char buf[ 256 ]; ssize_t bytes_read = read( fd, buf, 255 ); if ( bytes_read == -1 ) err( 1, "reading from %s", "zt.p4_state" ); close_or_warn( fd, "zt.p4_state" ); buf[ bytes_read ] = '\0'; return strcmp( buf, contents ) == 0; } int main( void ) { int file_fd = open( "zt.p4_state", O_WRONLY | O_CREAT | O_TRUNC, 0644 ); if ( file_fd == -1 ) err( 1, "truncating %s", "zt.p4_state" ); int sock_fds[ 2 ]; if ( socketpair( AF_UNIX, SOCK_DGRAM, 0, sock_fds ) == -1 ) err( 1, "socketpair" ); int server_fd = sock_fds[ 0 ], client_fd = sock_fds[ 1 ]; const char *message = "Words like violence" "Break the silence" "Come crashing in" "Into my little world" "Painful to me" "Pierce right through me" "Can't you understand?" "Oh, my little girl"; pid_t pid = fork_server( sock_fds, file_fd, message ); assert(( news_update( file_fd, client_fd ) == 0 )); assert( kill_and_reap( pid ) == 0 ); assert( test_file_contains( message ) ); close_or_warn( file_fd, "zt.p4_state" ); unlink_if_exists( "zt.p4_state" ); close_or_warn( client_fd, "client socket" ); close_or_warn( server_fd, "server socket" ); return 0; }