Programmaufrufe innerhalb von C/C++-Programmen
weiter zu Aufruf von Programmen in WindowsAufruf von Programmen in Linux, Teil 3
Aufräumarbeiten: Fehlerbehandlung
zurück zu Aufruf von Programmen in Linux, Teil 2
Zur Erinnerung das Vorgehen in Kurzform:
- Einen Kind-Prozess erzeugen mit
vfork()oderfork() - Mit
exec()den Kind-Prozess durch das neue Programm ersetzen - Aufräumarbeiten:
- Im Fehlerfall von exec() den Kind-Prozess beenden (Teil 1)
- Das Auftreten von Zombies verhindern (Teil 2):
- entweder über
signal(SIGCHLD, SIG_IGN) - oder über
waitpid().
- entweder über
- Fehlerbehandlung (Teil 3)
- Unerwünschte Dateideskriptoren (file descriptors) aufräumen (Teil 4)
Fehlerbehandlung
An vielen Stellen des Programms silidock können Fehler auftreten.
Bisher wurden Fehler allenfalls durch eine Fehlermeldung per "cerr" ausgegeben. Solche Meldungen sind jedoch
nur dazu geeignet, den Quellcode übersichtlicher zu machen. Den User*innen helfen sie in den seltensten
Fällen weiter.
Nicht alle Fehler eignen sich für eine Meldung. User*innen sollten die Möglichkeit haben, etwas an dem
Fehler zu ändern. Normalerweise beheben sie beispielsweise keine Programmierfehler. Es macht daher nicht
viel Sinn, die Beschreibung solcher Fehler in einem Fenster anzuzeigen.
Hilfreiche Fehlermeldungen sind z.B. Zugriffsfehler, die durch sudo-Provilegien behoben werden können,
nicht ausführbare Dateien, die durch chmod ausführbar gemacht werden können, falsche Pfadangaben...
Die genauere Eingrenzung des Fehlers ermöglicht im Idealfall seine Behebung. Dazu muss die Fehlermeldung
irgendwie angezeigt werden. In Windows gib es dafür die Standard-"MessageBox"; in Linux gibt es nichts
vergleichbares. Die silidock ist mit dem Toolkit FLTK programmiert, das praktischerweise
auch eine Fehlermeldung beinhaltet (fl_alert() ). Davon wird im Folgenden ausgiebig Gebrauch gemacht.
Fehler eingrenzen durch errno - number of last error
Header: <errno.h>:
Viele System-Funktionen und -Bibliotheken setzten im Fehlerfall einen bestimmten Integerwert, der
einem Fehlertyp entspricht, z.B. 13: EACCES Permission denied.
Das ist der "Wert des letzten Fehlers", errno. Taucht ein neuer Fehler auf, wird der Wert überschrieben.
Mit waitpid (siehe Teil 2) lässt sich auch prüfen, ob der Programmaufruf erfolgreich verlaufen ist bzw. der Grund
ermitteln, warum er fehlgeschlagen ist. Im Fehlerfall gibt waitpid -1 zurück.
WUNTRACED sorgt dafür, dass auch ein Wert zurückgegeben werden soll, wenn der Kind-Prozess gestoppt hat.
Der durch waitpid
verursachte Fehler ist für die meisten User*innen wenig hilfreich (ECHILD: nicht existierende PID
oder EINTR: Funktion unterbrochen).
Interessanter sind die Fehlerfälle der exec-Funktion, die vor waitpid im Eltern-Prozess
unterschieden
werden können, wie verweigerter Zugriff oder ungültige Pfadangabe. Daran können User*innen etwas ändern.
Im folgenden Code-Ausschnitt wird eine Fehlermeldung über einen FLTK-Dialog (fl_alert() ) ausgegeben. Es
ist sinnvoll, nicht nur eine Fehlermeldung auszugeben, wenn waitpid fehlschlägt, obwohl sie das
normalerweise auch tut, wenn beispielsweise der Pfad ungültig ist, sondern mit
else if ( WIFEXITED( child_status ) && WEXITSTATUS( child_status ) != 0)
abzufragen, ob der Kind-Prozess fehlschlägt.
Allerdings hat der Status, der mit
WEXITSTATUS(child_status) abgefragt werden kann, keinen weiteren Sinn, deshalb wird darauf
verzichtet ihn auszugeben.
Ein Fehlerwert aus waitpid überschreibt den Fehlerwert aus execv, deshalb wird der
Fehlerwert von execv zunächst abgespeichert in der Variable child_error bevor
waitpid aufgerufen wird.
waitpid gibt manchmal eine 0 zurück, obwohl der Prozess fehlschlägt und eigentlich eine -1 zurückgegeben
werden müsste. Wir haben es hier mit zwei Prozessen zu tun, die nicht aufeinander warten. usleep(50) sorgt
dafür, dass dem Kind-Prozess ein wenig Zeit gegeben wird, um den richtigen Rückgabewert zu ermitteln.
else // this is executed by parent process
{
usleep(50); // give some time to get status of child: 50 microseconds
// get und store useful errors of exec:
string child_error = "";
if ( errno == EACCES)
{
child_error = "\n Permission denied or process file not executable.";
}
else if ( errno == ENOENT)
{
child_error = "\n Invalid path or file.";
}
else if ( errno == EPERM)
{
child_error = "\n Superuser privileges required.";
}
else if ( errno == ENOEXEC)
{
child_error = "\n Unsupported format of file.";
}
else
{
child_error = "\n unexpected error:" + errno;
}
int child_status;
if ( waitpid(child, &child_status, WNOHANG | WUNTRACED) < 0) // waitpid failed
{
string waitpid_message = "Error - Execution failed: \n " + string(program_call);
string message = waitpid_message + child_error;
fl_alert( message.c_str() );
Fl::run();
}
else if ( WIFEXITED( child_status ) && WEXITSTATUS( child_status ) != 0) // child process failed although waitpid does not
{
string waitpid_message = "Error - Process failed: \n " + string(program_call);
string message = waitpid_message + child_error;
fl_alert( message.c_str() );
Fl::run();
}
}
Auch die Funktion fork() kann fehlschlagen. Das kann entweder daran liegen, dass bereits zu viele
Prozesse laufen und die maximale Anzahl überschritten wird oder zu wenig Speicherplatz zur Verfügung
steht.
Entsprechend der Fehlerausgabe im Eltern-Prozess, können auch diese Fehlerfälle über errno genauer
bestimmt und ausgegeben werden:
else if (child == -1) // fork error: child < 0
{
string fork_note = "fork failed for program: \n" + string(program_call);
string fork_error = "";
if (errno == EAGAIN)
{
fork_error = "\n To much processes";
}
else if (errno == ENOMEM)
{
fork_error = "\n Not enough space available.";
}
else
{
fork_error = "\n "\n Unexpected error: " + errno;
}
string message = fork_note + fork_error;
fl_alert( message.c_str() );
Fl::run();
}
Der Code-Schnipsel zum Aufruf sieht dann folgendermaßen aus:
(dies ist ein verkürzter Ausschnitt aus dem Quellcode von silidock)
// C++ Header
#include <iostream.h>
#include <cerrno.h>
// C-Header
extern "C" {
#include <unistd.h> // fork, execv, getpid
#include <sys/wait.h> // for signal, waitpid
#include <errno.h> // fork, execv, getpid
}
void execute_program(const char* program_call, const char* param )
{
pid_t child = vfork();
if(child == 0) // executed by child process:
{
int child_pid = getpid();
// replace the child process with exec*-function
char *args[2]; // arguments for exec
args[0] = (char*)program_call; // first argument is program_call
args[1] = (char*)param;
args[2] = NULL; // no arguments...
execv(program_call,args);
// executes if execv failed
_exit(2);
}
else if (child == -1) // fork error: child < 0
{
string fork_note = "fork failed for program: \n" + string(program_call);
string fork_error = "";
if (errno == EAGAIN)
{
fork_error = "\n To much processes";
}
else if (errno == ENOMEM)
{
fork_error = "\n Not enough space available.";
}
else
{
fork_error = "\n "\n Unexpected error: " + errno;
}
string message = fork_note + fork_error;
fl_alert( message.c_str() );
Fl::run();
}
else // this is executed by parent process
{
usleep(50); // give some time to get status of child: 50 microseconds
// get und store useful errors of exec:
string child_error = "";
if ( errno == EACCES)
{
child_error = "\n Permission denied or process file not executable.";
}
else if ( errno == ENOENT)
{
child_error = "\n Invalid path or file.";
}
else if ( errno == EPERM)
{
child_error = "\n Superuser privileges required.";
}
else if ( errno == ENOEXEC)
{
child_error = "\n Unsupported format of file.";
}
else
{
child_error = "\n unexpected error:" + errno;
}
int child_status;
if ( waitpid(child, &child_status, WNOHANG | WUNTRACED) < 0) // waitpid failed
{
string waitpid_message = "Error - Execution failed: \n " + string(program_call);
string message = waitpid_message + child_error;
fl_alert( message.c_str() );
Fl::run();
}
else if ( WIFEXITED( child_status ) && WEXITSTATUS( child_status ) != 0) // child process failed although waitpid does not
{
string waitpid_message = "Error - Process failed: \n " + string(program_call);
string message = waitpid_message + child_error;
fl_alert( message.c_str() );
Fl::run();
}
// prevent zombies:
pid_t p;
// Reap all pending child processes
do {
p = waitpid(-1, NULL, WNOHANG);
} while (p != (pid_t)0 && p != (pid_t)-1);
}
}
weiter zu Teil 4: Dateideskriptoren