#define _POSIX_C_SOURCE 200809L #include /* assert */ #include /* err, errx, warn */ #include /* errno, ENOENT */ #include /* uint8_t */ #include /* close, fork, ftruncate, lseek, read, unlink, write */ /* V této přípravě je Vaším úkolem naprogramovat klient pro * jednoduchý spojovaný protokol pro přenos bloků dat (Váš klient * bude data odesílat). Po připojení klienta server potvrdí * připravenost přijímat data jednobajtovou zprávou ‹0x01›. * * Klient tak může odeslat první 512bajtový blok, načež vyčká na * potvrzení od serveru, který odpoví opět jednobajtovou zprávou – * ‹0x01› v případě úspěchu a ‹0x02› v případě, že data nemohla být * zapsána. Klient vždy před odesláním dalšího bloku vyčká na * potvrzení. Konec přenosu klient oznámí zavřením spojení. * * Procedura ‹bput› obdrží popisovač již připojeného spojovaného * socketu a popisovač souboru, ze kterého bude data načítat. * Není-li velikost předaného souboru dělitelná 512, procedura se * ihned vrátí s chybou -3 (aniž by jakkoliv komunikovala * s protistranou). * * Aby mohla být data ze souboru efektivně odstraněna, budeme bloky * odesílat od konce souboru a každý blok, kterého příjem byl * protistranou potvrzen, ze souboru odstraníme voláním ‹ftruncate›. * * Návratová hodnota ‹bput› bude: * * • 0 v případě úspěchu – server potvrdil všechny zprávy a soubor * s daty k odeslání je prázdný, * • -3 je-li předán nesprávný datový soubor, * • -2 v případě, že byla komunikace ukončena kvůli selhání na * straně serveru (server indikoval, že data nebyl schopen * uložit), * • -1 v případě, že nastala chyba komunikace nebo systémová chyba * na straně klienta. * * Do výstupního parametru ‹block_count› pak uloží počet bloků, * které byly úspěšně odeslány a potvrzeny, bez ohledu na celkový * výsledek. Vlastníkem obou popisovačů je volající. */ int bput( int sock_fd, int file_fd, int *block_count ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* open */ #include /* open */ #include /* exit */ #include /* waitpid */ #include /* socketpair */ 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, "unlink" ); } static int file_size( int fd ) { int size = lseek( fd, 0, SEEK_END ); if ( size == -1 ) err( 1, "seek" ); return size; } static void file_rewind( int fd ) { if ( lseek( fd, 0, SEEK_SET ) == -1 ) err( 1, "seek" ); } static void copy_fd( int sock_fd, int file_fd, int bytes ) { while ( bytes ) { char buffer[ 32 ]; int nbytes = bytes > 32 ? 32 : bytes; int bytes_read = read( sock_fd, buffer, nbytes ); if ( bytes_read == -1 ) err( 1, "reading data from client" ); if ( bytes_read == 0 ) err( 1, "client unexpectedly closed the connection" ); int bytes_written = write( file_fd, buffer, bytes_read ); if ( bytes_written == -1 ) err( 1, "server writing to file" ); if ( bytes_written != bytes_read ) errx( 1, "short write on the server" ); bytes -= bytes_read; } } static void write_or_die( int fd, const uint8_t *buffer, int nbytes ) { int bytes_written = write( fd, buffer, nbytes ); if ( bytes_written == -1 ) err( 1, "writing %d bytes", nbytes ); if ( bytes_written != nbytes ) errx( 1, "unexpected short write: %d/%d written", bytes_written, nbytes ); } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "waitpid" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } static int create_file( const char *name ) { unlink_if_exists( name ); int fd = open( name, O_CREAT | O_TRUNC | O_RDWR, 0666 ); if ( fd == -1 ) err( 2, "creating %s", name ); return fd; } static int fork_server( int *client_fd, int *file_fd, void( *server )( int, int ), int close_fd ) { const char *file_name = "zt.p6_server.bin"; int fds[ 2 ]; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, fds ) == -1 ) err( 1, "socketpair" ); int server_fd = fds[ 0 ]; *client_fd = fds[ 1 ]; *file_fd = create_file( file_name ); int server_pid = fork(); if ( server_pid == -1 ) err( 1, "fork" ); if ( server_pid == 0 ) { close_or_warn( *client_fd, "client end of the socket" ); close_or_warn( close_fd, "client state file" ); server( server_fd, *file_fd ); close_or_warn( *file_fd, file_name ); close_or_warn( server_fd, "server end of the socket" ); exit( 0 ); } close_or_warn( server_fd, "server end of the socket" ); return server_pid; } static void server_1( int sock_fd, int file_fd ) { uint8_t status = 1; write_or_die( sock_fd, &status, 1 ); copy_fd( sock_fd, file_fd, 512 ); write_or_die( sock_fd, &status, 1 ); copy_fd( sock_fd, file_fd, 512 ); write_or_die( sock_fd, &status, 1 ); assert( read( sock_fd, &status, 1 ) == 0 ); } static void server_2( int sock_fd, int file_fd ) { uint8_t status = 1; write_or_die( sock_fd, &status, 1 ); copy_fd( sock_fd, file_fd, 512 ); write_or_die( sock_fd, &status, 1 ); copy_fd( sock_fd, file_fd, 512 ); status = 2; write_or_die( sock_fd, &status, 1 ); assert( read( sock_fd, &status, 1 ) == 0 ); } static void server_3( int sock_fd, int file_fd ) { ( void ) file_fd; uint8_t status = 1; assert( read( sock_fd, &status, 1 ) == 0 ); } int main( void ) { const char *cfile_name = "zt.p6_client.bin"; uint8_t buffer[ 2048 ] = { 0 }; int client_fd, server_pid, block_count; int cfile_fd, sfile_fd, null_fd_wr, null_fd_rd; int bytes_read; buffer[ 13 ] = 1; cfile_fd = create_file( cfile_name ); write_or_die( cfile_fd, buffer, 1024 ); file_rewind( cfile_fd ); null_fd_wr = open( "zt.p6_null", O_WRONLY | O_CREAT, 0666 ); null_fd_rd = open( "zt.p6_null", O_RDONLY ); if ( null_fd_wr == -1 || null_fd_rd == -1 ) err( 1, "opening zt.p6_null" ); /* case 1 */ server_pid = fork_server( &client_fd, &sfile_fd, server_1, cfile_fd ); assert( bput( client_fd, cfile_fd, &block_count ) == 0 ); close_or_warn( client_fd, "client fd" ); assert( block_count == 2 ); assert( reap( server_pid ) == 0 ); /* check and reset */ bytes_read = pread( sfile_fd, buffer, 2048, 0 ); assert( bytes_read == 1024 ); assert( buffer[ 525 ] == 1 ); assert( file_size( cfile_fd ) == 0 ); close_or_warn( sfile_fd, "server data file" ); /* case 2 */ write_or_die( cfile_fd, buffer, 1024 ); file_rewind( cfile_fd ); server_pid = fork_server( &client_fd, &sfile_fd, server_2, cfile_fd ); assert( bput( client_fd, cfile_fd, &block_count ) == -2 ); close_or_warn( client_fd, "client fd" ); assert( block_count == 1 ); assert( reap( server_pid ) == 0 ); assert( file_size( cfile_fd ) == 512 ); close_or_warn( sfile_fd, "server data file" ); /* case 3 & 4 */ assert( bput( null_fd_wr, cfile_fd, &block_count ) == -1 ); close_or_warn( client_fd, "client fd" ); server_pid = fork_server( &client_fd, &sfile_fd, server_3, cfile_fd ); write_or_die( cfile_fd, buffer, 13 ); assert( bput( client_fd, cfile_fd, &block_count ) == -3 ); close_or_warn( client_fd, "client fd" ); assert( reap( server_pid ) == 0 ); close_or_warn( sfile_fd, "server data file" ); close_or_warn( null_fd_rd, "zt.p6_null" ); unlink_if_exists( "zt.p6_null" ); close_or_warn( cfile_fd, cfile_name ); unlink_if_exists( cfile_name ); return 0; }