#define _POSIX_C_SOURCE 200809L #include /* assert */ #include /* uint8_t */ #include /* access, alarm, close, dup, fork, read, rmdir, unlinkat, write */ /* Podprogram ‹log_server› bude realizovat jednoduchý proudový * protokol, kdy protistrana bude odesílat záznamy ve formátu * ‹jméno\0zpráva\0› a ‹log_server› uloží každou přijatou zprávu * (včetně nulového ukončovacího bajtu) na konec souboru ‹jméno› ve * složce ‹dir_fd›. * * Neexistuje-li příslušný soubor, ‹log_server› jej vytvoří. Je * přípustné pro každou zprávu příslušný soubor otevřít, zprávu * zapsat, a opět jej uzavřít. * * Protokol vyžaduje, aby ‹jméno› nepřesáhlo 512 bajtů a byl dodržen * formát zpráv (v opačném případě skončí ‹log_server› s chybou -2). * Dojde-li k systémové chybě, výsledek bude -1. Podaří-li se * všechny záznamy uložit a klient ukončí spojení, výsledek bude 0. * Popisovač ‹sock_fd› je ve vlastnictví volajícího. * * Délka samotné zprávy není nijak omezena, server ale přesto musí * pracovat s fixním množstvím paměti. */ int log_server( int dir_fd, int sock_fd ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* closedir, fdopendir, rewinddir */ #include /* err, errx, warn */ #include /* errno, EEXIST, ENOENT */ #include /* openat, unlinkat */ #include /* exit */ #include /* memcmp, strcmp */ #include /* socketpair */ #include /* mkdir */ #include /* waitpid */ int open_or_die( int dir_fd, const char *path, int flags ) { int fd = openat( dir_fd, path, flags, 0644 ); if ( fd == -1 ) err( 1, "error opening %s", path ); return fd; } static void unlink_if_exists( int dir, const char *name ) { if ( unlinkat( dir, name, 0 ) == -1 && errno != ENOENT ) err( 2, "unlinking %s", name ); } static void rmdir_or_die( const char *name ) { if ( rmdir( name ) == -1 ) err( 2, "unlinking %s", name ); } static void mkdir_or_die( const char* dirname ) { if ( mkdir( dirname , 0755 ) == -1 && errno != EEXIST ) err( 2, "mkdir" ); } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static void closedir_or_warn( DIR *dir ) { if ( closedir( dir ) == -1 ) warn( "closing dir" ); } static void socketpair_or_die( int type, int *sv ) { if ( socketpair( AF_UNIX, type, 0, sv ) == -1 ) err( 2, "creating socketpair" ); } 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 int fork_client( int fds[ 2 ], int dir_fd, char *buf, size_t size ) { int *sock_client = fds, *sock_server = fds + 1; pid_t pid = fork(); alarm( 2 ); if ( pid == -1 ) err( 2, "fork" ); if ( pid == 0 ) // child → client { close_or_warn( *sock_client, "sock client" ); close_or_warn( dir_fd, "directory" ); write_or_die( *sock_server, buf, size ); close_or_warn( *sock_server, "sock server" ); exit( 0 ); } close_or_warn( *sock_server, "sock server" ); return pid; } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "waiting for %d", pid ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } static void rm_dir_content( int dir_fd ) { int fd = dup( dir_fd ); if ( fd < 0 ) err( 2, "duping dir fd, fd %d", dir_fd ); DIR *dir = fdopendir( fd ); if ( dir == NULL ) err( 2, "opening dir to dirent, fd %d", fd ); rewinddir( dir ); struct dirent *dirent; errno = 0; while ( ( dirent = readdir( dir ) ) != NULL ) { if ( !( strcmp( dirent->d_name, "." ) && strcmp( dirent->d_name, ".." ) ) ) continue; unlink_if_exists( dir_fd, dirent->d_name ); } if ( errno != 0 ) err( 2, "reading dir, fd %d", fd ); closedir_or_warn( dir ); } static void access_or_die( const char *name, int amode ) { if ( access( name, amode ) != 0 ) err( 2, "access file %s", name ); } static void rm_dir_or_die( const char *name ) { int fd = openat( AT_FDCWD, name, O_DIRECTORY ); if ( fd < 0 ) { if ( errno != ENOENT ) err( 2, "opening dir %s", name ); return; } rm_dir_content( fd ); close_or_warn( fd, name ); rmdir_or_die( name ); } static void assert_exist( char **files, int n ) { for ( int i = 0; i < n; ++i ) access_or_die( files[ i ], F_OK ); } static int file_contains( const char *file, const char *contents, int size ) { int fd = open_or_die( AT_FDCWD, file, O_RDONLY ); char buf[ 256 ]; ssize_t bytes_read = read( fd, buf, sizeof( buf ) ); if ( bytes_read == -1 ) err( 1, "reading from %s", file ); if ( bytes_read != size ) err( 2, "unexpected short read %s", file ); close_or_warn( fd, file ); return memcmp( buf, contents, size ) == 0; } int main( void ) { const char *dir_path = "zt.r6_dir"; rm_dir_or_die( dir_path ); int dir_fd, fds[ 2 ], *sock_client = fds; pid_t pid; char *files[ 4 ] = { "zt.r6_dir", "zt.r6_dir/ab.txt", "zt.r6_dir/cd", "zt.r6_dir/thelongestnameicouldhavecreatedbecauseiwantedtotestyourkn" "owledgeandthinkingintheclanguageprobablystillihaventfilledinyourbuf" "fersoiamstillwritingnewlettersbecauseiliketobreakyourprogramswhynot" "whyyeswhynotandyesatthesametimebutstillitsnotfilledsoicanwrite" }; mkdir_or_die( dir_path ); const uint8_t data_1[] = "ab.txt\0test content\0"; size_t size_1 = sizeof( data_1 ) - 1; const uint8_t data_2[] = "cd\0a\0ab.txt\0 test content\0"; size_t size_2 = sizeof( data_2 ) - 1; const uint8_t data_3[] = "ab.txt\0test content \0thelongestnameicouldhavecreatedbecauseiwanted" "totestyourknowledgeandthinkingintheclanguageprobablystillihaventfil" "ledinyourbuffersoiamstillwritingnewlettersbecauseiliketobreakyourpr" "ogramswhynotwhyyeswhynotandyesatthesametimebutstillitsnotfilledsoic" "anwrite\0cannot be created, sorry\0cd\0a\0"; size_t size_3 = sizeof( data_3 ) - 1; dir_fd = open_or_die( AT_FDCWD, dir_path, O_DIRECTORY ); // Test case 1. socketpair_or_die( AF_UNIX, fds ); pid = fork_client( fds, dir_fd, ( char * ) data_1, size_1 ); assert( log_server( dir_fd, *sock_client ) == 0 ); assert( reap( pid ) == 0 ); assert_exist( files, 2 ); assert( file_contains( files[ 1 ], "test content\0", 13 ) == 1); close_or_warn( *sock_client, "sock client" ); // Test case 2. socketpair_or_die( AF_UNIX, fds ); pid = fork_client( fds, dir_fd, ( char * ) data_2, size_2 ); assert( log_server( dir_fd, *sock_client ) == 0 ); assert( reap( pid ) == 0 ); assert_exist( files, 3 ); assert( file_contains( files[ 1 ], "test content\0 test content\0", 27 ) == 1); assert( file_contains( files[ 2 ], "a\0", 2 ) == 1); close_or_warn( *sock_client, "sock client" ); // Test case 3. socketpair_or_die( AF_UNIX, fds ); pid = fork_client( fds, dir_fd, ( char * ) data_3, size_3 ); assert( log_server( dir_fd, *sock_client ) == 0 ); assert( reap( pid ) == 0 ); assert_exist( files, 4 ); assert( file_contains( files[ 1 ], "test content\0 test content\0test content \0", 41 ) == 1); assert( file_contains( files[ 2 ], "a\0a\0", 4 ) == 1); assert( file_contains( files[ 3 ], "cannot be created, sorry\0", 25 ) == 1); close_or_warn( *sock_client, "sock client" ); // Test case 4. socketpair_or_die( AF_UNIX, fds ); pid = fork_client( fds, dir_fd, ( char * ) data_1, size_1 - 1 ); assert( log_server( dir_fd, *sock_client ) == -2 ); assert( reap( pid ) == 0 ); close_or_warn( *sock_client, "sock client" ); // Test case 5. socketpair_or_die( AF_UNIX, fds ); pid = fork_client( fds, dir_fd, ( char * ) data_1, 6 ); assert( log_server( dir_fd, *sock_client ) == -2 ); assert( reap( pid ) == 0 ); close_or_warn( *sock_client, "sock client" ); close_or_warn( dir_fd, dir_path ); rm_dir_or_die( dir_path ); return 0; }