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:
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 ;) !
Er bloggt schneller als man gucken kann ;-)