#define _POSIX_C_SOURCE 200809L #include /* htonl */ #include /* assert */ #include /* err, errx, warn */ #include /* errno, ENOENT */ #include /* openat */ #include /* int32_t, uint32_t */ #include /* exit */ #include /* socketpair */ #include /* waitpid */ #include /* alarm, close, fork, lseek, read, unlink, write */ /* Napište podprogram ‹count_server›, který přijme 3 parametry: * * 1. ‹sock_fd› je popisovač datagramového socketu, * 2. ‹file_fd› je popisovač obyčejného souboru, * 3. ‹count› je počet datagramů, které mají být zpracovány. * * Server udržuje počítadlo v rozsahu ⟨-2³¹, 2³¹). Toto počítadlo je * uloženo v souboru určeném popisovačem ‹file_fd›. Příchozí * datagramy obsahují čtyřbajtové číslo se znaménkem (v dvojkovém * doplňkovém kódu, nejvýznamnější bajt první). * * Server po přijetí každé zprávy upraví hodnotu uloženou v souboru * (ve stejném formátu – dvojkový doplňkový kód, nejvýznamnější bajt * první). * * Návratová hodnota: * * • 0 znamená, že bylo úspěšně zpracováno ‹count› datagramů, * • -1 znamená systémovou chybu, * • -2 chybu protokolu (datagram nesprávné velikosti), * • -3 chybu ve vstupním souboru (nesprávná velikost). */ int count_server( int sock_fd, int file_fd, int count ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ const char *out_file = "zt.r5_counter"; static void unlink_if_exists( const char *file ) { if ( unlink( file ) == -1 && errno != ENOENT ) err( 2, "unlink" ); } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } 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 void write_or_die( int fd, const char *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 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 void lseek_or_die( int fd, off_t offset, int whence ) { int status = lseek( fd, offset, whence ); if ( status < 0 ) err( 2, "lseek fd %d", fd ); } static void socketpair_or_die( int type, int *sv ) { if ( socketpair( AF_UNIX, type, 0, sv ) == -1 ) err( 2, "creating socketpair" ); } 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 ); } static int read_or_die( int fd, void *buffer, int nbytes ) { int bytes_read = read( fd, buffer, nbytes ); if ( bytes_read == -1 ) err( 1, "reading %d bytes", nbytes ); return bytes_read; } int32_t check_file_or_die( int file_fd ) { lseek_or_die( file_fd, 0, SEEK_SET ); int32_t num; assert( read_or_die( file_fd, &num, 5 ) == 4 ); return htonl( num ); } static int fork_server( int fds[ 2 ], int32_t *data, size_t size, int check_fd, int file_fd ) { int status = 0, *server_fd = fds, *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; } // newly created process alarm( 3 ); close2_or_warn ( file_fd, "output file", check_fd, "check file" ); close_or_warn( *client_fd, "client socket forked" ); for ( size_t i = 0; i < size; ++ i ) { int32_t number = htonl( data[ i ] ); send_or_die( *server_fd, &number , 4 ); } close_or_warn ( *server_fd, "server socket forked" ); exit( status ); } 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 write_start_or_die( int fd, void *buf, int size ) { lseek_or_die( fd, 0, SEEK_SET ); write_or_die( fd, ( void * ) buf, size ); lseek_or_die( fd, -size, SEEK_CUR ); } int main( void ) { alarm( 5 ); unlink_if_exists( out_file ); unsigned char zeroes[ 6 ] = { 0 }; int file_fd = open_or_die( out_file, O_CREAT | O_TRUNC | O_RDWR ); write_start_or_die( file_fd, zeroes, 4 ); int check_fd = open_or_die( out_file, O_RDONLY ); int fds[ 2 ]; int *server_fd = fds, *client_fd = fds + 1 ; pid_t pid; int32_t data1[] = { 0, 1, 1 }; int data1_s = sizeof ( data1 ) / sizeof ( int32_t ); int32_t data2[] = { 0, 1, 1, -355, 3500, 36694, -6554 }; int data2_s = sizeof ( data2 ) / sizeof ( int32_t ); int32_t data3[] = { 67, -101, 115, -115, 110, 97, -32, 49, -55, 50, -32, 106, -101, 1500, -32, 108, -101, 104, -107, 121, -32, 106, -101, 100, -110, 111, -109, -1256, 111, 116, 111, -598, 114, 111, 118, 121, 32, 108, 101, 116, 111, 117, -110, -46, -32, -86, -105, -97, -99, -32, -110, -97, -32, -104, -116, 116, 112, -2024, 115, 58, 47, 47, -115, 98, 111, -32023, -46, -115, -107, -47, 67, -69, 86, 120, 100 }; int data3_s = sizeof ( data3 ) / sizeof ( int32_t ); // test 1 - basic counting socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, data1, data1_s, check_fd, file_fd ); assert( count_server( *client_fd, file_fd, data1_s ) == 0 ); assert( reap( pid ) == 0 ); assert( check_file_or_die( check_fd ) == 2 ); close_or_warn ( *client_fd, "client socket" ); // test 2 - counting with previous value socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, data2, data2_s, check_fd, file_fd ); assert( count_server( *client_fd, file_fd, data2_s ) == 0 ); assert( reap( pid ) == 0 ); assert( check_file_or_die( check_fd ) == 33287 + 2 ); close_or_warn( *client_fd, "client socket" ); // test 3 - setting new default value (defined by user) *( ( uint32_t * ) zeroes ) = 0xffffffff; // -1 write_start_or_die( file_fd, zeroes, 4 ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, data2, data2_s, check_fd, file_fd ); assert( count_server( *client_fd, file_fd, data2_s ) == 0 ); assert( reap( pid ) == 0 ); assert( check_file_or_die( check_fd ) == 33287 - 1 ); close_or_warn( *client_fd, "client socket" ); // test 4 - testing on more data socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, data3, data3_s, check_fd, file_fd ); assert( count_server( *client_fd, file_fd, data3_s ) == 0 ); assert( reap( pid ) == 0 ); assert( check_file_or_die( check_fd ) == 0 ); close_or_warn( *client_fd, "client socket" ); // test 5 - smaller datagram size socketpair_or_die( SOCK_DGRAM, fds ); pid = fork(); if ( pid == -1 ) err( 2, "fork" ); if ( pid == 0 ) { close2_or_warn ( file_fd, "output file", check_fd, "check file" ); close_or_warn( *client_fd, "client socket forked" ); send_or_die( *server_fd, data1 , 1 ); close_or_warn( *server_fd, "server socket forked" ); exit( 0 ); } assert( count_server( *client_fd, file_fd, data2_s ) == -2 ); assert( reap( pid ) == 0 ); close2_or_warn( *client_fd, "client socket", *server_fd, "server socket" ); // test 6 - bigger file size than allowed write_start_or_die( file_fd, zeroes, 6 ); socketpair_or_die( SOCK_DGRAM, fds ); pid = fork_server( fds, data1, data1_s, check_fd, file_fd ); assert( count_server( *client_fd, file_fd, data2_s ) == -3 ); assert( reap( pid ) == 0 ); close_or_warn( *client_fd, "client socket" ); close2_or_warn ( file_fd, out_file, check_fd, out_file ); // test 7 - write-only file, it can return any of the return codes, // but the return code must be an error code int null_fd = open_or_die( "/dev/null", O_WRONLY ); assert( count_server( null_fd, null_fd, data1_s ) < 0 ); close_or_warn( null_fd, "/dev/null" ); unlink_if_exists( out_file ); return 0; }