externen process ausführen
-
hi
habe eine klasse geschrieben um externe binarys usw auszuführen und den stdout sowie stderr strukturiert mitzuloggen. das ganze hat auch gut funktioniert nur beim versuch ls auf ein directory mit vielen dateien (> 3000) auszuführen bleibt das program stecken, zum code:
string CProcess::executeProcess(string cmd, bool stopOnReturnErrorCode) /* * method is used to execute an external process, this is done by emulating popen via popen2 * this allows to catch the stdout and the stderr at once * the stdout and stderr is written to the logger and the stdout is returned as result * if the stopOnReturnErrorCode flag is set AND a error code is returned by an executed process then the program is stopped * */ { LOGGER.log(_MY_NAME_, "now starting to execute command '" + cmd + "'", DEBUG2); string returnMsgOut = ""; string returnMsgErr = ""; FILE* pipes[2]; // pid_t pid = popen2(pipes, "/bin/ls -lR /"); pid_t pid = popen2(pipes, cmd.c_str()); if (pid == -1) { LOGGER.log("CProcess::executeProcess", "could not create pipe using popen2, cmd=\""+cmd+"\"", ERROR); } const int len=256; char bufferErr[len]; char bufferOut[len]; while (!feof(pipes[0]) || !feof(pipes[1])) { if(fgets(bufferErr, len, pipes[1]) != NULL) { // cout << "<stdout>" << endl << bufferErr << "</stdout>" << endl; returnMsgErr += bufferErr; } if(fgets(bufferOut, len, pipes[0]) != NULL) { // cout << "<stderr>" << endl << bufferOut << "</stderr>" << endl; returnMsgOut += bufferOut; } } int returnCode = pclose2(pid, pipes); if (returnMsgOut[returnMsgOut.length()-1] == '\n') { // avoid the linebreak in the logger, this is need i.e. for pwd command returnMsgOut[returnMsgOut.length()-1] = ' '; } if (returnMsgErr[returnMsgErr.length()-1] == '\n') { // avoid the linebreak in the logger returnMsgErr[returnMsgErr.length()-1] = ' '; } stringstream ss; ss << returnCode; string returnCodeString (ss.str()); string logMsg = "\n<command>" + cmd + "</command>\n<returnMessage>" + returnMsgOut + "</returnMessage><errMessage>" + returnMsgErr + "</errMessage>\n<returnCode>" + returnCodeString + "</returnCode>"; LOGGER.log("CProcess::executeProcess", logMsg + "", DEBUG2); if (stopOnReturnErrorCode && returnCode!=0) { // check if the return code is something different form 0 LOGGER.log("CProcess::executeProcess", "your last command returned an error code", ERROR); } string::size_type loc = logMsg.find( "error", 0); string::size_type locTeX = logMsg.find("\"(That makes 100 errors; please try again.)\" (TeX)", 0); /* * check if the return message contains the word error * gromacs sometimes returns the message above, which is no real error * so to stop: the stop flag must be set, the message must conatin error but not the Tex message */ if(stopOnReturnErrorCode && loc != string::npos && locTeX == string::npos) { LOGGER.log("CProcess::executeProcess", "your last command returned the word \"error\" in the return-message", ERROR); } return returnMsgOut; // return only the content of out, i.e. from pwd: "/home/bknapp" } pid_t CProcess::popen2(FILE* files[], char const* command) /* * method emulates the popen via pipe and execl, stdout and stderr are caught */ { static const int PIPE_READ = 0; static const int PIPE_WRITE = 1; int outfd[2], errfd[2]; pid_t pid; if (pipe(outfd) == -1 || pipe(errfd) == -1) { LOGGER.log("CProcess::popen2", "could not open pipes", WARNING); // the error will be thrown because of the -1 return return -1; } pid = fork(); if (pid == -1) { LOGGER.log("CProcess::popen2", "could not fork", WARNING); // the error will be thrown because of the -1 return return -1; } if (pid == 0) { /* child process */ /* no errors are reported to the caller from here on */ dup2(outfd[PIPE_WRITE], STDOUT_FILENO); /* redireceting stdout */ dup2(errfd[PIPE_WRITE], STDERR_FILENO); /* redireceting stderr */ close(outfd[PIPE_READ]); /* close reading end */ close(errfd[PIPE_READ]); execl("/bin/sh", "sh", "-c", command, NULL); /* shell call */ LOGGER.log("CProcess::popen2", "execl failed", WARNING); exit(0); /* if excel fails end it */ } close(outfd[PIPE_WRITE]); /* close writing ends */ close(errfd[PIPE_WRITE]); files[0] = fdopen(outfd[PIPE_READ], "r"); /* return reading end to the caller */ files[1] = fdopen(errfd[PIPE_READ], "r"); if (files[0] == NULL || files[1] == NULL) { LOGGER.log("CProcess::popen2", "fdopen failed", ERROR); } return pid; }
wenn ich das ganze debugge dann bleibt er in der zeile mit if(fgets(bufferErr, len, pipes[1]) != NULL) gleich beim ersten mal hängen (stürzt nicht ab aber geht auch nicht weiter im code)
scheint irgendwo ein buffer überzulaufen, weiss aber nicht wie ich das lösen soll ... len auf einen höheren wert zu setzten hilft a nixjemand eine idee?
lg
lordy
-
keiner eine idee? oder is das problem zu unklar beschrieben?
wenn ich zb
executeProcess("ls /home/sepp/", true) aufrufe funktioniert es (enthält etwa 25 datein)wenn ich aber
executeProcess("ls /home/sepp/pdb-files/", true) bleibt er in zeile 25 hängen ohne jedoch abzustürzen, scheint auf irgendwas zu warten (enthält ca 3000 dateien). abwarten hilft auch nix, nach 30 min ist er auch nicht weiter ...lg
lordy
-
Das Problem wird sein, dass Dein Programm zuerst darauf wartet, dass etwas in den Errorstrom geschrieben wird oder der auf EOF läuft (fgets ist blockierend!). Das Unterprogramm widerum beendet sich nicht, weil es darauf wartet, dass irgendjemand die Daten, die es auf stdout geschrieben hat, liest.
klassischer Deadlock.
Bei "kleinen" Verzeichnissen wird einfach der Puffer des Betriebssystem groß genug sein um die gesamte Ausgabe aufzunehmen, wodurch ls sich beenden kann, wodurch widerum Dein fgets auf den Errorstrom EOF liefert und Dein Programm zum Lesen der Ausgabe übergeht.
-
hi
danke für deine antwort, macht natürlich sinn deine argumentation, frage ist nur wie ich das lösen soll. gibt es eine äquivalente funktion zu fgets die nicht blockiert? oder was wäre eine vernünftige lösung in dem fall?
ich hab noch eine andere lösung geschrieben wo der error stream nicht gesondert behandelt wird und dort funktionierts wunderbar
string CProcess::executeProcessNoErrorPiping (string cmd, bool stopOnReturnErrorCode) /* * This method uses popen and pclose to receive a stream of text from a system process. Error stream and std out stream are not handeled seperatly. * params: * cmd: command to execute * stopOnReturnErrorCode: if your command has a return code which is not 0, the LOGGER is called with param ERROR * return: the messages recived from the started process */ { char psBuffer[128]; FILE *pPipe; string returnMsg; string logMsg; int returnCode; if((pPipe = popen(cmd.c_str(), "r")) == NULL) { char errorBuff [256]; sprintf(errorBuff, "could not execute command '%s'.\n", cmd.c_str()); LOGGER.log("CProcess::executeProcess", errorBuff, ERROR); } else { /* Read pipe until end of file. */ while(!feof(pPipe)) { if(fgets(psBuffer, 128, pPipe) != NULL) // printf(psBuffer); returnMsg = returnMsg + psBuffer; } /* Close pipe and print return value of pPipe. */ returnCode = pclose(pPipe); if (returnMsg[returnMsg.length()-1] == '\n') { // avoid the linebreak in the logger returnMsg[returnMsg.length()-1] = ' '; } stringstream ss; ss << returnCode; string returnCodeString (ss.str()); logMsg = "the command \"" + cmd + "\" returns the message :\"" + returnMsg + "\" and returnCode is: " + returnCodeString + ""; LOGGER.log("CProcess::executeProcess", logMsg + "", DEBUG2); if (stopOnReturnErrorCode && returnCode!=0) { // check if the return code is something different form 0 LOGGER.log("CProcess::executeProcess", "your last command returned an error code", ERROR); } } return returnMsg; }
frage ist nur wie kombinier ich die beiden, dass der error stream gesondert behandelt wird und große dateinmengen angenommen werden dürfen? (für ls ist es kein problem wenn ich den error stream nicht gesondert habe, aber bei anderen komplexeren programmen sehr wohl)
lg
lordy