#define _POSIX_C_SOURCE 200809L #include /* assert */ #include /* err, errx, warn */ #include /* errno, ENOENT */ #include /* exit */ #include /* memcmp, strlen */ #include /* recv, send, socketpair */ #include /* open */ #include /* open, openat */ #include /* waitpid */ #include /* alarm, close, fork, read, unlink, write */ /* Naprogramujte proceduru ‹tftp_get›, která ze serveru protokolu * TFTP¹ stáhne zadaný soubor a zapíše ho do předaného popisovače * obyčejného souboru. Socket, který je proceduře ‹tftp_get› předán * má nastavenu implicitní adresu příjemce (je „připojený“). * * Výsledkem je: * * • ‹-2› server ohlásil chybu, * • ‹-1› nastala systémová chyba na straně klientu, * • ‹0› soubor byl úspěšně převzat a uložen. * * ¹ TODO popsat RRQ část protokolu. */ int tftp_get( const unsigned char *id, int id_len, int sock_fd, int out_fd ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ const char *in_file = "zt.r6_tftpr_in"; const char *out_file = "zt.r6_tftpr_out"; static void unlink_if_exists( const char *file ) { if ( unlink( file ) == -1 && errno != ENOENT ) err( 2, "unlink" ); } static int open_or_die( const char *name, int oflag ) { int fd = openat( AT_FDCWD, name, oflag, 0644 ); if ( fd == -1 ) err( 2, "opening %s", name ); return fd; } static int create_file( const char *name ) { unlink_if_exists( name ); int fd = open_or_die( name, O_CREAT | O_TRUNC | O_RDWR ); return fd; } static int open_and_write( const char *name, const unsigned char *data, ssize_t len ) { int fd = create_file( name ); if ( write( fd, data, len ) != len ) err( 2, "writing file %s", name ); return fd; } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static int cmp_output( const char *name, const unsigned char *expected, int size ) { char buffer[ 256 ]; int fd = open( name, O_RDONLY ); if ( fd == -1 ) err( 2, "opening %s", name ); int rv = 0; int out_size = 0; while ( ( rv = read( fd, buffer, sizeof( buffer ) ) ) > 0 ) { if ( out_size + rv > size ) { rv = -1; goto end; } if ( memcmp( expected + out_size, buffer, rv ) != 0 ) { rv = -1; goto end; } out_size += rv; } end: close_or_warn( fd, name ); return out_size != size ? -1 : rv; } static int read_or_die( int fd, char *buffer, int nbytes ) { int bytes_read = read( fd, buffer, nbytes ); if ( bytes_read == -1 ) err( 1, "reading %d bytes", nbytes ); return bytes_read; } static void send_or_die( int fd, const void *buffer, int nbytes ) { int bytes_sent = send( fd, buffer, nbytes, 0 ); if ( bytes_sent == -1 ) err( 1, "sending %d bytes", nbytes ); if ( bytes_sent != nbytes ) errx( 1, "unexpected short send: %d/%d sent", bytes_sent, nbytes ); } static int send_file( int client_fd, const char *name, const int error_when ) { char buf[ 2048 ], ans_buf[ 512 + 4 ], packet = 1; ans_buf[ 0 ] = 0x00; ans_buf[ 1 ] = 0x03, ans_buf[ 2 ] = 0x00; int name_len = strlen( name ), size, last_size = 0; ssize_t received = recv( client_fd, buf, sizeof( buf ), 0); if ( received == -1 ) return -1; assert( received == name_len + 9 ); assert( buf[ 0 ] == 0x00 && buf[ 1 ] == 0x01 ); assert( strncmp( buf + 2, name, name_len + 1 ) == 0 ); assert( strncmp( buf + 3 + name_len, "octet\0", 6 ) == 0 ); int fd = open_or_die( in_file, O_RDONLY ); while ( ( size = read_or_die( fd, ans_buf + 4, 512 ) ) > 0 || last_size == 512 ) { if ( packet == error_when ) // want to send an error { ans_buf[ 1 ] = 0x05; ans_buf[ 2 ] = 0x00; ans_buf[ 3 ] = 0x00; ans_buf[ 4 ] = 0x00; send_or_die( client_fd, ans_buf, 5 ); break; } ans_buf[ 3 ] = packet; send_or_die( client_fd, ans_buf, size + 4 ); received = recv( client_fd, buf, sizeof( buf ), 0 ); // ACK or ERROR if ( received == 4 ) { assert( buf[ 0 ] == 0x00 && buf[ 1 ] == 0x04 ); assert( buf[ 2 ] == 0x00 && buf[ 3 ] == packet ); } else // should be error packet { assert( received > 4 ); assert( buf[ 0 ] == 0x00 && buf[ 1 ] == 0x05 ); assert( packet == -error_when ); assert( buf[ 2 ] == 0x00 ); assert( buf[ received - 1 ] == 0x00 ); break; } last_size = size; ++ packet; } close_or_warn( fd, in_file ); return 0; } static int fork_server( int fds[ 2 ], const char *name, const int error_when) { 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 ) { close_or_warn( server_fd, "server socket" ); return child_pid; } alarm( 3 ); close_or_warn( client_fd, "client socket" ); if ( send_file( server_fd, name, error_when ) == -1 ) err( 2, "receive file" ); close_or_warn( server_fd, "server socket" ); exit( 0 ); } static void socketpair_or_die( int type, int *sv ) { if ( socketpair( AF_UNIX, type, 0, sv ) == -1 ) err( 2, "creating socketpair" ); } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 1, "waitpid" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); return 1; } static void close2_or_warn( const int fd1, const char *name1, const int fd2, const char *name2 ) { close_or_warn( fd1, name1 ); close_or_warn( fd2, name2 ); } int main( void ) { alarm( 5 ); unlink_if_exists( in_file ); unlink_if_exists( out_file ); int fds[ 2 ], *client_fd = ( fds + 1 ), out_fd; pid_t pid; char *file_name = "file.txt"; const char *data1 = { "Roses are red,\n" "violets are blue,\n" "hello world\n" "possibly from Linux/GNU.\n" }; char data2[ 4096 ]; for ( int i = 0; i < 256; ++ i ) data2[ i ] = ( char ) i; memset( data2 + 256, 's', 128 ); memset( data2 + 256 + 128, 'a', 503 ); memset( data2 + 256 + 128 + 503, 'b', 55 ); memset( data2 + 256 + 128 + 503 + 55, 0x04, sizeof( data2 ) - 256 - 128 - 503 - 55 ); // test 1 close_or_warn( open_and_write( in_file, ( unsigned char * ) data1, strlen( data1 ) ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 0 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == 0 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); assert( cmp_output( out_file, ( unsigned char * ) data1, strlen( data1 ) ) == 0 ); // test 2 close_or_warn( open_and_write( in_file, ( unsigned char * ) data2, 511 ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 0 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == 0 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); assert( cmp_output( out_file, ( unsigned char * ) data2, 511 ) == 0 ); // test 3 close_or_warn( open_and_write( in_file, ( unsigned char * ) data2, 512 ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 0 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == 0 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); assert( cmp_output( out_file, ( unsigned char * ) data2, 512 ) == 0 ); // test 4 close_or_warn( open_and_write( in_file, ( unsigned char * ) data2, 515 ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 0 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == 0 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); assert( cmp_output( out_file, ( unsigned char * ) data2, 515 ) == 0 ); // test 5 close_or_warn( open_and_write( in_file, ( unsigned char * ) data2, 2024 ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 0 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == 0 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); assert( cmp_output( out_file, ( unsigned char * ) data2, 2024 ) == 0 ); // test 6 close_or_warn( open_and_write( in_file, ( unsigned char * ) data2, sizeof( data2 ) ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 0 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == 0 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); assert( cmp_output( out_file, ( unsigned char * ) data2, sizeof( data2 ) ) == 0 ); // test 7 - system error socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, -1 ); out_fd = open( "/dev/null", O_RDONLY ); assert( out_fd >= 0 ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == -1 ); close2_or_warn( out_fd, "/dev/null", *client_fd, "client socket" ); assert( reap( pid ) == 0 ); // test 8 - error code sent close_or_warn( open_and_write( in_file, ( unsigned char * ) data2, sizeof( data2 ) ), "in file" ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, file_name, 4 ); out_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_WRONLY ); assert( tftp_get( ( unsigned char * ) file_name, strlen( file_name ), *client_fd, out_fd ) == -2 ); close2_or_warn( out_fd, out_file, *client_fd, "client socket" ); assert( reap( pid ) == 0 ); unlink_if_exists( in_file ); unlink_if_exists( out_file ); return 0; }