#define _POSIX_C_SOURCE 200809L #include /* STDOUT_FILENO, STDERR_FILENO */ #include /* dprintf */ #include /* err, warn */ /* Uvažme program, který vypisuje řádky jak na svůj standardní * výstup, tak na svůj standardní chybový výstup, přičemž tyto mohou * být mezi sebou různě prokládány. Pokud tento program necháme * běžet v terminálu bez přesměrování, budou řádky obou výstupů * promíchané a nebudeme schopni rozlišit, který řádek pochází * z kterého. Jeho dva standardní výstupy jistě můžeme přesměrovat * každý na jiné místo (v shellu například použitím ‹">"› a ‹"2>"›), * nicméně v tom případě naopak ztratíme informaci o tom, jak byly * výstupy vzájemně proloženy. * * Cílem tohoto úkolu je implementovat proceduru ‹combine›, která * pro zadaný program s parametry přesměruje jeho výstupy na * společný popisovač, avšak zároveň podle prefixu odliší původ * každého řádku. * * Procedura ‹combine› bere následující parametry: * * • ‹argv› – ukazatel na pole řetězců, zakončený ‹NULL›, jehož * první prvek je název programu a ostatní jsou jeho parametry * (název programu se však už neopakuje); * • ‹fd_out› – popisovač, na který mají být přesměrovány oba * výstupy; * • ‹status› – ukazatel, na který bude zapsán výsledný status * skončeného procesu. * * Programem pro spuštění může být i takový, který se nachází * pod některou cestou uvedenou v proměnné prostředí ‹PATH›. * * Hodnotu pod ukazatelem ‹status› bude možné předložit makrům * ‹WIFEXITED›, ‹WEXITSTATUS›, ‹WIFSIGNALED› a ‹WTERMSIG› pro * zjištění informací o tom, jak byl spuštěný program ukončen. * * Procedura ‹combine› spustí program se zadanými argumenty * a přesměruje jeho výstup tak, aby byl vypisován na objekt zadaný * popisovačem ‹fd_out› níže popsaným způsobem. * * Každý řádek standardního výstupu programu bude vypsán nezměněný, * pouze doplněný zepředu o prefix ‹"[STDOUT] "› (až do maximální * velikosti, viz popis níže). Stejným způsobem bude zpracován * jeho standardní chybový výstup, který bude doplňován o prefix * ‹"[STDERR] "›. * * Řádky na výstupu budou omezeny na délku 71 znaků (nepočítaje * prefix ani znak konce řádku). Tedy se započítáním prefixu bude * každý řádek možné zobrazit nejvýše 80 znaky. Znaky, které by * přečnívaly tento limit, budou zahozeny. Zároveň bude na řádku * následujícím po vypsaném uvedeno, že došlo k zahození znaků * a kolik takových bylo (ani zde se nebude počítat znak konce * řádku) v následujícím formátu: * * ‹"STDOUT: cropped N characters"› * * Zde ‹N› označuje počet zahozených znaků a ‹"STDOUT"› bude * nahrazeno za ‹"STDERR"› pro chybový výstup. * * V závislosti na spouštěném programu může nastat situace, že * některý výstup bude ukončen bez toho, aby obsahoval zakončovací * znak ‹'\n'›. V takovém případě nechť je takový řádek vypsán * stejným způsobem jako výše, pouze doplněn o tento znak. * Zároveň nechť je informace o tomto uvedena na následujícím * samostatném řádku ve formátu: * * ‹"STDOUT: no newline"› * * Podobně jako výše pro chybový výstup bude ‹"STDOUT"› nahrazeno za * ‹"STDERR"›. Pokud nastane situace, že daný neukončený řádek byl * zároveň zkrácen, nechť je informace o chybějícím zakončení * uvedena jako «druhá». * * Pro užitečnost procedury ‹combine› je nutné, aby byly řádky * vypisovány postupně a bez zbytečné prodlevy. Zejména to znamená, * že pokud na jeden z výstupů program zapsal data, není možné čekat * s jejich zpracováním ani v případě, že na druhý výstup je * neustále zapisováno. * * Za předpokladu, že všechny vstupní řádky jsou nejvýše zadané * délky (71 znaků), nechť platí, že mezi okamžikem, kdy je na jeden * výstup programem zapsán znak ‹'\n'›, a okamžikem vypsání * odpovídajícího řádku není více než 284 vypsaných znaků z druhého * výstupu. * * Podprogram ‹combine› skončí, jakmile je spouštěný program * ukončen. * * Jeho návratovou hodnotou nechť je 0 v případě úspěšného spuštění * zadaného programu a vyčkání na jeho ukončení. Pokud dojde * k nenapravitelné systémové chybě, návratová hodnota bude -1. * * Jedinou výjimkou je případ, kdy k nenapravitelnému selhání dojde * ve vedlejším procesu. Protože takovou chybu by bylo obtížné * zachycovat z podprogramu ‹combine›, toto selhání se bude * indikovat pouze skrze hodnotu pod ukazatelem ‹status›. Na něj * nechť je v takovém případě zapsána taková hodnota, která odpovídá * ukončení procesu s návratovým kódem 100. */ int combine( char **argv, int fd_out, int *status ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* assert */ #include /* NULL, exit */ #include /* strlen */ #include /* errno */ #include /* alarm, fork */ #include /* wait */ #include /* open */ 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 *name ) { if ( unlink( name ) == -1 && errno != ENOENT ) err( 2, "unlinking %s", name ); } static int run_combine( char **argv, int *status ) { const char *name = "zt.c_out"; int fd = open( name, O_WRONLY | O_CREAT | O_TRUNC, 0666 ); if ( fd == -1 ) err( 2, "opening %s", name ); int rv = combine( argv, fd, status ); close_or_warn( fd, name ); return rv; } static int check_output( const char *expected ) { const char *name = "zt.c_out"; char buffer[ 255 + 1 ] = { 0 }; int fd = open( name, O_RDONLY ); if ( fd == -1 ) err( 2, "opening %s", name ); if ( read( fd, buffer, 255 ) == -1 ) err( 2, "reading %s", name ); close_or_warn( fd, name ); return strcmp( expected, buffer ); } int main( int argc, char **argv ) { if ( argc >= 2 ) { int status; int rv = combine( argv + 1, STDOUT_FILENO, &status ); return rv; } int status; char *args1[] = { "echo", "hello world", NULL }; assert( run_combine( args1, &status ) == 0 ); assert( WIFEXITED( status ) && WEXITSTATUS( status ) == 0 ); assert( check_output( "[STDOUT] hello world\n" ) == 0 ); char *args2[] = { "printf", "ooga\\nbooga", NULL }; assert( run_combine( args2, &status ) == 0 ); assert( WIFEXITED( status ) && WEXITSTATUS( status ) == 0 ); assert( check_output( "[STDOUT] ooga\n" "[STDOUT] booga\n" "STDOUT: no newline\n") == 0 ); char *args3[] = { "sh", "-c", ">&2 echo some error", NULL }; assert( run_combine( args3, &status ) == 0 ); assert( WIFEXITED( status ) && WEXITSTATUS( status ) == 0 ); assert( check_output( "[STDERR] some error\n" ) == 0 ); unlink_if_exists( "zt.c_out" ); return 0; }