/* * runtree */ #include #include #include #include #include #include #include #include #include #include #define MAX_PATH 2048 /* Deklaration von Funktionen, die bei der Programmierung von * main benötigt werden und die im Rahmen dieser * Aufgabe (zusätzlich zu main) zu programmieren sind */ /* traversiert rekursiv das jeweilige Verzeichnis */ void traverse_dir( char* dir_name, char* command ); /* startet einen Sohn-Prozess */ void run_command( char* command, char* file_name ); /* Signalhandler für SIGCHLD */ void sigchld_handler( int sig ); /* Deklaration von Funktionen, die bei der Programmierung * benötigt werden und die im Rahmen dieser Aufgabe bereits * vorgegeben sind. */ /* Installiert den SIGCHLD-Signalhandler */ void install_signalhandler(); /* Blockiert das Signal SIGCHLD */ void block_sigchld(); /* Deblockiert das Signal SIGCHLD */ void unblock_sigchld(); /* Wartet auf einen einzelnen Sohn-Prozess */ int wait_for_child(); /* * Globale Variablen */ /* maximale Anzahl der Sohn-Prozesse */ const int max_children=3; /* aktuelle Anzahl von Sohn-Prozessen */ int number_of_children=0; /* * Funktion main */ int main( int argc, char** argv ) { /* Überprüfen der korrekten Anzahl von Argumenten */ if( argc != 3 ) { fprintf( stderr, "usage: %s ", argv[0] ); exit( EXIT_FAILURE ); } /* * Funktionsaufruf zum Installieren des Signal-Handler */ install_signalhandler(); /* * Funktionsaufruf zum Traversieren der Verzeichnisse */ traverse_dir( argv[1], argv[2] ); /* warten auf noch laufende Sohn-Prozesse */ block_sigchld(); while( (number_of_children > 0) && (wait_for_child() > 0) ) ; return 0; } /* * Die Funktion traverse_dir öffnet das jeweilige Verzeichnis * und iteriert anschließend über die Verzeichniseinträge. * Ist der Verzeichniseintrag selber wieder ein Verzeichnis * so ruft sich die Funktion rekursiv auf. Bei Verzeichnis- * einträgen von regulären Dateien wird die Funktion * run_command aufgerufen. */ void traverse_dir( char* dir_name, char* command ) { struct dirent* entry; struct stat fstatus; DIR* dir; char name_buf[MAX_PATH]; /* Verzeichnis öffnen */ if( (dir = opendir( dir_name )) == NULL ) { fprintf( stderr, "Fehler beim Öffnen des Verzeichnisses %s: %s\n", dir_name, strerror(errno) ); return; } /* Die Funktion soll hier beginnen über alle Verzeichnis- * einträge zu iterieren. Verzeichniseinträge, welche mit * einem Punkt beginnen, sollen übersprungen werden. */ while( (entry = readdir(dir)) != NULL ) { if( entry->d_name[0] == '.' ) continue; /* Aus jedem Verzeichniseintrag wird zusammen mit * dem aktuellen Verzeichnisnamen ein kompletter * Path-Name gebildet. Path-Namen, welche länger * als MaX_PATH sind, werden übersprungen. */ if( (strlen(dir_name) + strlen(entry->d_name) + 2) > MAX_PATH ) continue; strcpy( name_buf, dir_name ); if( dir_name[strlen(dir_name)-1] != '/' ) strcat( name_buf, "/" ); strcat( name_buf, entry->d_name ); /* Mit dem kompletten Path-Namen ist der Dateistatus * abzufragen. Handelt es sich bei dem Verzeichniseintrag * um ein Verzeichnis, so wird die Funktion traverse_dir * rekursiv aufgerufen, bei einer regulären Datei wird * run_command aufgerufen. Symbolische Links werden * nicht verfolgt */ if( lstat( name_buf, &fstatus ) == -1 ) { fprintf( stderr, "Fehler beim Holen des Dateistatus von %s: %s\n", name_buf, strerror(errno) ); continue; } if( S_ISDIR(fstatus.st_mode) ) traverse_dir( name_buf, command ); else if( S_ISREG(fstatus.st_mode) ) run_command( command, name_buf ); } /* Hier endet die Iteration */ closedir(dir); } /* * Die Funktion run_command erzeugt einen neuen Sohn-Prozess * und startet in diesem das Kommando mit dem Dateinamen als * erstem Argument. */ void run_command( char* command, char* file_name ) { pid_t pid; char* arguments[3]; /* * Ausfüllen des Argumenten-Vektors */ arguments[0] = command; arguments[1] = file_name; arguments[2] = NULL; /* SIGCHLD-Signal blockieren, damit Prozess-Zähler * konsistent bleibt */ block_sigchld(); /* Ist die maximale Anzahl von Sohn-Prozessen erreicht * dann soll an dieser Stelle auf das Ende eines Sohn- * Prozesses gewartet werden */ if( (number_of_children >= max_children) && !(wait_for_child > 0) ) return; /* An dieser Stelle soll nun ein neuer Sohn-Prozess * erzeugt werden. Dieser führt das Kommando aus. * Der Vater-Prozess erhöht anschliessend seinen * Kind-Prozesszähler. */ if( (pid = fork()) > 0 ) number_of_children++; else if( pid == 0 ) { execvp( command, arguments ); fprintf( stderr, "Kommando %s ausführen fehlgeschlagen: %s\n", command, strerror(errno) ); } else perror( "Sohn-Prozess erzeugen fehlgeschlagen" ); /* Vor dem Verlassen der Funktion muss das Signal SIGCHLD * wieder deblockiert werden */ unblock_sigchld(); } /* * Signalhandler für SIGCHLD installieren */ void install_signalhandler() { struct sigaction action; memset( &action, 0, sizeof(action) ); sigemptyset( &action.sa_mask ); action.sa_flags = 0; action.sa_handler = sigchld_handler; if( sigaction( SIGCHLD, &action, NULL ) == -1 ) { perror( "Fehler beim Installieren des Signalhandlers" ); exit( EXIT_FAILURE ); } } /* * Signalhandler für SIGCHLD. * In diesem Signalhandler sollen alle zu diesem Zeitpunkt * beendeten Sohn-Prozesse aufgeräumt werden. */ void sigchld_handler( int sig ) { pid_t ret; int errno_bak = errno; while( (ret = waitpid( -1, NULL, WNOHANG )) != -1 ) { if( ret == 0 ) break; number_of_children--; } errno = errno_bak; } /* * Signal SIGCHLD blockieren. */ void block_sigchld() { sigset_t set; sigemptyset( &set ); sigaddset( &set, SIGCHLD ); sigprocmask( SIG_BLOCK, &set, NULL ); } /* * Signal SIGCHLD deblockieren. */ void unblock_sigchld() { sigset_t set; sigemptyset( &set ); sigaddset( &set, SIGCHLD ); sigprocmask( SIG_UNBLOCK, &set, NULL ); } /* * Die Funktion wait_for_child wartet auf das Beenden eines * einzelnen Sohn-Prozesses. (Diese Funktion ist nicht für * den signalhandler geeignet!) */ int wait_for_child() { pid_t pid; while( pid = wait(NULL) ) { if( pid < 0 ) { if( errno == EINTR ) continue; perror(""); return -1; } number_of_children--; return pid; } return 0; }