Systemaufrufe, Linux, Windows, Rückgabewerte und der Errorstream

Heute folgendes Problem gehabt: Es soll ein Systemaufruf an openssl ts zur Signierung von Timestamps (siehe hier) erfolgen. Das an sich ist ja erst mal noch kein Problem. Allerdings soll die geschriebene Klasse gleichermaßen unter Linux und Windows mit möglichst aussagekräftigen Fehlermeldungen zum Einsatz kommen. Jetzt ist die pikante Sache daran, dass der ts-Parameter von openssl erst ab Version 0.99 mit dabei ist, die standardmäßig unter Debian Lenny nicht mit installiert ist. Jetzt erzeugt netterweise ein Aufruf von openssl ts einen Returnwert von 0 (= alles okay), obwohl openssl den ts-Befehl in der installierten Version garnicht kennt – wir haben also keine Möglichkeit über eine Prüfung des Returnwertes alleine über Erfolg oder Misserfolg der Funktion zu entscheiden:

OpenSSL ts

OpenSSL ts

Als Waffe der Wahl haben wir uns nun also für exec entschieden, da shell_exec keinen Returnwert zurückliefert und system / passthru den Programmoutput einfach rausfeueren und nicht returnen. Zur Erinnerung, exec sieht so aus:

string exec ( string $befehl [, array $ausgabe [, int $return_var ]] ) 

Den returnten String von exec kann man sich dabei schenken, der beinhaltet nur die letzte Zeile des Arrays aus dem zweiten Parameter. Erster naiver Ansatz also (wohlgemerkt in einer openssl-Version, die den ts-Befehl nicht kennt):

$retarray = array(); exec("openssl ts", $retarray, $retcode);  print_r($retarray); //Array ( ) var_dump($retcode); //int(0) 

Wie oben schon erwähnt: Der Returncode 0 hilft uns nichts, das Return-Array ist leer – und das obwohl es aus openssl nur so heraussprudelt, wenn man openssl ts auf der Konsole direkt ausführt. Warum? Ins Return-Array werden nur Programmausgaben auf den Standard-Input gepackt. Die „gescreenshottete“ Ausgabe oben erfolgt jedoch auf STDERR. Blöd. Die Lösung?

2>&1 

Jaja, richtig gelesen – Umleitung von stderr auf stdout. Damit:

$retarray = array(); exec("openssl ts 2>&1", $retarray, $retcode);  print_r($retarray);  /* Array (     [0] => openssl:Error: 'ts' is an invalid command.     [1] =>      [2] => Standard commands     [3] => asn1parse      ca             ciphers        crl            crl2pkcs7     [4] => dgst           dh             dhparam        dsa            dsaparam     [5] => enc            engine         errstr         gendh          gendsa     [6] => genrsa         nseq           ocsp           passwd         pkcs12     [7] => pkcs7          pkcs8          prime          rand           req     [8] => rsa            rsautl         s_client       s_server       s_time     [9] => sess_id        smime          speed          spkac          verify     [10] => version        x509     [11] =>      [12] => Message Digest commands (see the `dgst' command for more details)     [13] => md2            md4            md5            mdc2           rmd160     [14] => sha            sha1     [15] =>      [16] => Cipher commands (see the `enc' command for more details)     [17] => aes-128-cbc    aes-128-ecb    aes-192-cbc    aes-192-ecb    aes-256-cbc     [18] => aes-256-ecb    base64         bf             bf-cbc         bf-cfb     [19] => bf-ecb         bf-ofb         cast           cast-cbc       cast5-cbc     [20] => cast5-cfb      cast5-ecb      cast5-ofb      des            des-cbc     [21] => des-cfb        des-ecb        des-ede        des-ede-cbc    des-ede-cfb     [22] => des-ede-ofb    des-ede3       des-ede3-cbc   des-ede3-cfb   des-ede3-ofb     [23] => des-ofb        des3           desx           idea           idea-cbc     [24] => idea-cfb       idea-ecb       idea-ofb       rc2            rc2-40-cbc     [25] => rc2-64-cbc     rc2-cbc        rc2-cfb        rc2-ecb        rc2-ofb     [26] => rc4            rc4-40         rc5            rc5-cbc        rc5-cfb     [27] => rc5-ecb        rc5-ofb     [28] =>  ) */  var_dump($retcode); //int(0) 

Schon besser. Damit lässt sich dann mit einer Konstruktion dieser Art drangehen:

if ($retcode === 0 && stripos($retarray[0], "openssl:Error") === false) {     //alles okay, "wirklicher" Erfolg } else {     //Fehler } 

Damit wäre die Geschichte schonmal abgeklärt. Aber es lauert noch eine weitere Gemeinheit: Unter Linux ergibt ein Aufruf an ein nicht existierendes Programm den Returncode 127:

$retarray = array(); exec("foobar_this_aint_a_programm 2>&1", $retarray, $retcode); print_r($retarray);  /* Array (     [0] => sh: foobar_this_aint_a_programm: not found ) */  var_dump($retcode); //int(127) 

Nicht so jedoch unter Windows – Hier gibts den Returncode 1 – was sich wieder äußerst toll von einem existenten, aber fehlerhaften Aufruf unterscheiden lässt:

$retarray = array(); exec("foobar_this_aint_a_programm 2>&1", $retarray, $retcode); print_r($retarray);  /* Array (     [0] => Der Befehl "foobar_this_aint_a_programm" ist entweder falsch geschrieben oder     [1] => konnte nicht gefunden werden. ) */  var_dump($retcode); //int(1) 

Es muss doch was schöneres geben als das parsen des umgeleiteten stderr-Streams, oder? Tipps erbeten ;) !


Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0