Horst Nogajski: Beenden einer Operation abwarten, vor Ausführung der Nächsten

Hallo zusammen,

mittels exec(und einigen parametern) erstelle ich eine temporäre Datei. Das funktioniert wunderbar.
Diese Datei "$fname" (es ist eine Binäre) lese ich mit folgender Funktion in eine Variable namens $myfile:

function getFile($fname) {
    $myfile = "";
        if ($fd = fs_fopen($fname, "r")) {
                while (!feof($fd)) {
                        $myfile .= fread($fd, 65536);
                }
                fclose($fd);
        }
    return $myfile;
}

Nachdem ich die Datei in meiner Variablen $myfile sozusagen virtuell gespeichert habe, möchte ich mittels unlink($fname); die temporäre Datei von der Festplatte löschen.

Es kommt aber leider zu konflikten in der Form,
das entweder die Datei zu schnell gelöscht wird, und so meine virtuelle Datei $myfile leer ist, oder er ist vorhanden, (und wird im weitern Script auch im Browser ausgegeben) aber es ist nicht möglich die Datei $fname zu löschen.
Stattdessen gibt es eine Fehlermeldung wie: unlink failed (permission denied), und im Error-Log steht auch, das es nicht Möglich ist die Datei zu löschen da sie gerade von einem anderen Prozess verwendet wird.

Wie kann ich dem Script beibringen, erst die Datei komplett in die Variable zu lesen, und danach von der Festplatte zu löschen?

Für jede Hilfe dankbar,
Horst.
dann

  1. Moin

    Es kommt aber leider zu konflikten in der Form,
    das entweder die Datei zu schnell gelöscht wird, und so meine virtuelle Datei $myfile leer ist, oder er ist vorhanden, (und wird im weitern Script auch im Browser ausgegeben) aber es ist nicht möglich die Datei $fname zu löschen.
    Stattdessen gibt es eine Fehlermeldung wie: unlink failed (permission denied), und im Error-Log steht auch, das es nicht Möglich ist die Datei zu löschen da sie gerade von einem anderen Prozess verwendet wird.

    Hmm, mir ist noch nicht ganz klar wo der andere Prozess herkommen soll. exec() wartet üblicherweise bis das aufgerufene Programm fertig ist. Daran kann es also kaum liegen (es sei denn du hast mit & etwas anderes bestimmt, dann solltest du das nochmal überdenken)
    Und in PHP selbst werden die Befehle traditionellerweise einer nach dem anderen ausgeführt.

    Wie kann ich dem Script beibringen, erst die Datei komplett in die Variable zu lesen, und danach von der Festplatte zu löschen?

    Mein Vorschlag:
    <?php
    exec("wasauchimmer"); // Beachte das fehlende & am Ende
    $huhu=getFile($dada);
    unlink($dada);
    ?>

    Das funktioniert jedenfalls hier absolut prima.

    --
    Henryk Plötz
    Grüße von der Ostsee

    1. Hallo Henryk Plötz,
      erstmal viele Grüße vom Westzipfel (Aachen) an die Ostsee.

      Hmm, mir ist noch nicht ganz klar wo der andere Prozess herkommen soll. exec() wartet üblicherweise bis das aufgerufene Programm fertig ist. Daran kann es also kaum liegen (es sei denn du hast mit & etwas anderes bestimmt, dann solltest du das nochmal überdenken)
      Und in PHP selbst werden die Befehle traditionellerweise einer nach dem anderen ausgeführt.

      Ja, ich hatte mich verstrickt und es lag so Einiges im Argen.
      Heute hab' ich alles nochmal neu geschrieben.

      Es klappt jetzt auch hier, ich kann die Datei in die Variable einlesen und danach löschen.

      Was aber noch nicht klappt, ist die Ausgabe der binären Daten der Variable am Browser. Mit print oder echo bekomme ich nur eine Textausgabe.
      Die Daten sind aber ein Bild. Wie teilt man dem Browser mit, das er Binärdaten zeigen soll?

      Vieln Dank für jede Hilfe.
      Horst

      1. Moin

        Was aber noch nicht klappt, ist die Ausgabe der binären Daten der Variable am Browser. Mit print oder echo bekomme ich nur eine Textausgabe.
        Die Daten sind aber ein Bild. Wie teilt man dem Browser mit, das er Binärdaten zeigen soll?

        Dazu musst du dem Browser den richtigen Content-Type mitliefern. Da PHP-Skripte normalerweise HTML-Output liefern, wird eben auch normalerweise text/html gesendet. Dafür benutzt du einfach header() (http://www.php.net/manual/en/function.header.php), etwa so:

        <?php
        header("Content-type: image/jpeg");
        ?>

        oder für gif-Bilder dementsprechend image/gif. Achtung: Der header()-Aufruf muss vor jeder anderen Ausgabe stehen. Also achte darauf, dass vor dem ersten <?php keine Leerzeilen oder -zeichen sind, und dass auch sonsts nichts weiter ausgegeben wird, bis der header()-Aufruf kommt.

        --
        Henryk Plötz
        Grüße von der Ostsee

        1. Auch Moin,

          Was aber noch nicht klappt, ist die Ausgabe der binären Daten der Variable am Browser. Mit print oder echo bekomme ich nur eine Textausgabe.
          Die Daten sind aber ein Bild. Wie teilt man dem Browser mit, das er Binärdaten zeigen soll?

          Dazu musst du dem Browser den richtigen Content-Type mitliefern. Da PHP-Skripte normalerweise HTML-Output liefern, wird eben auch normalerweise text/html gesendet. Dafür benutzt du einfach header() (http://www.php.net/manual/en/function.header.php), etwa so:

          <?php
          header("Content-type: image/jpeg");
          ?>

          Ja, das ist logisch, und ich hab's auch ausprobiert.
          Das ist bestimmt in Zukunft auch von Nutzen, -
          aber im Moment ist es leider nicht so einfach, denn ich möchte zuerst eine Tabelle erzeugen, in der dann unter "Anderem" auch das Bild platziert wird.

          Ich habe also meine Binär-Variable mit dem Bild, möchte im Body eine Tabelle erzeugen, in deren ersten Zelle kommt eine TextÜberschrift, in der 2. Zelle soll dann das Bild aus der Binär-Variablen erscheinen, und dann kommt nochmal eine Zelle mit Text.

          Somit habe ich ja mehrere content-types, nämlich text und jpeg und nochmal text?!?

          Horst

          1. Moin

            Ich habe also meine Binär-Variable mit dem Bild, möchte im Body eine Tabelle erzeugen, in deren ersten Zelle kommt eine TextÜberschrift, in der 2. Zelle soll dann das Bild aus der Binär-Variablen erscheinen, und dann kommt nochmal eine Zelle mit Text.
            Somit habe ich ja mehrere content-types, nämlich text und jpeg und nochmal text?!?

            Also das geht so einfach nicht. Traditionellerweise musst du nun dein Skript zweimal aufrufen, bzw. 2 Skripte nehmen. Ein Skript liefert den HTML-Code und den Klimbim ringsherum und das andere Skript sendet das Bild. Du musst dir dann noch ein Schema ausdenken, wie du dem Bild liefernden Skript sagst, was es liefern soll. Das ist sehr situationsspezifisch und dabei kann ich dir erstmal so nicht helfen.

            Wenn du aber gerne rumexperimentierst und wenn es dich nicht stört dass das in dem einen oder anderen Browser nicht geht, möchtest du dir evt. http://aktuell.de.selfhtml.org/artikel/grafik/inline-images/index.htm ansehen. Das Base64-Encodieren kannst du dabei in PHP von base64_encode() (http://www.php.net/manual/en/function.base64-encode.php) erledigen lassen.

            --
            Henryk Plötz
            Grüße von der Ostsee

            1. Hallo Henryk,

              Also das geht so einfach nicht....

              JaJa, das ist mir in den letzten Stunden mit ganzer Heftigkeit klar geworden.

              Traditionellerweise musst du nun dein Skript zweimal aufrufen, bzw. 2 Skripte nehmen. Ein Skript liefert den HTML-Code und den Klimbim ringsherum und das andere Skript sendet das Bild. Du musst dir dann noch ein Schema ausdenken, wie du dem Bild liefernden Skript sagst, was es liefern soll. Das ist sehr situationsspezifisch und dabei kann ich dir erstmal so nicht helfen.

              Ja, hier hab' ich noch nicht's weiter probiert.
              Wenn sich die Möglichkeiten mit 2 Skripten nur auf die unten Beschriebene Methode mit den Inline-Bildern bezieht, kommt sie nicht in Frage, was bedeuten würde, ich hab im Moment keinerlei Perspektive mehr. :(((
              Wenn Du noch andere Möglichkeiten weißt, gib' mir bitte noch nen Schups.

              Wenn du aber gerne rumexperimentierst und wenn es dich nicht stört dass das in dem einen oder anderen Browser nicht geht, möchtest du dir evt. http://aktuell.de.selfhtml.org/artikel/grafik/inline-images/index.htm ansehen. Das Base64-Encodieren kannst du dabei in PHP von base64_encode() (http://www.php.net/manual/en/function.base64-encode.php) erledigen lassen.

              Es war ein hoffnungversprechender Ansatz, nur leider stört es mich ganz massiv, das der InternetExplorer zu den Browsern gehört, die die inlineBilder nicht Anzeigen können. Das ist nicht vertretbar.
              Schade-Schade, das wär's gewesen.  :((
              __________________________

              Um aber noch mal auf deine vorherige Mail zurückzukommen, bezüglich des sendens von headern():
              Ich hab' zwischenzeitlich ne ganze Menge rumrecherchiert, was meinem aktuellen Problem zwar nicht zugutegekommen ist, aber trotzdem viel Neues und Nützliches ergeben hat.

              Grundsätzlich hast Du recht, das man header() nur senden kann/darf bevor man anderen HTML-Output erzeugt.
              ABER:
              (vielleicht weißt Du das ja schon, und wolltest mich nicht unnötig verwirren)
              Man kann innerhalb von PHP-Scripten (das dient dann hauptsächlich der Übersicht des Codes) schon mit echo"" oder print() oder mit HTML-Blöcken einen OutPut erzeugen, und später noch header() oder setcookie() senden.
              Man kann sich dazu den sogenannten Funktionen zur Ausgabesteuerung bedienen (english:http://www.php.net/manual/en/ref.outcontrol.php) (deutsch:http://www.php.net/manual/de/ref.outcontrol.php).
              Man startet am Anfang der Seite nur einen OutputBuffer mit
              <?PHP
              ob_start();
              ob_implicit_flush(0);

              // dann kommt der ganze ScriptKram wie bisher,
              // und wenn es nötig ist, auch mal (vielleicht Aufgrund von vorher
              // durchgeführten Abfragen, Berechnungen, wasweißich) einen Header
              // wie z.B. setcookie(x,y,z)
              // und dann wieder irgendwas mit echo"" print() usw.

              Diese OutPut-Kontrolle arbeitet so, das ALLES was mit echo, print() oder an HTML gesendet wird, in dem Puffer landet, und NICHT an den Browser gesendet wird. WOHL ABER werden alle Header() und
              setcookies() SOFORT an den Browser gesendet.

              Es gibt dann mehrere Möglichkeiten, mit der gepufferten Ausgabe etwas "anzustellen":
              Die einfachste Variante ist: "ob_end_flush();"
              (beendet das Puffern und sendet alles an den Browser)
              mit "ob_clean_flush();" wird das Puffern beendet und der Speicher gelöscht, ohne Ausgabe an den Browser.
              Vorher kann man mit z.B. $MeineGanzeAusgabe = ob_get_contents() die GESAMTE SEITE in einen String speichern, (dann den Puffer löschen)
              und $MeineGanzeAusgabe z.B. gzip-komprimieren und an gzip-fähige-Browser senden. (Bei Seiten mit viel Textinhalt sicher sehr nützlich)
              Nicht gzip-fähige-Browser bekommen eben den unkomprimierten String gesand.

              Das passende Script dazu, nebst ausführlicher Erklärung (und einiges mehr) gibt es hier: http://www.phpbuilder.com/columns/argerich20010125.php3?page=1
              Das Script ist auf der 2. Seite beschrieben!

              Hier schon mal das Script ohne weiteren Kommentar:
              <?PHP
              ob_start();
              ob_implicit_flush(0);
              ?>

              Hier zwischen steht dann unverändert der schon existierende Code!

              <?PHP
              $contents = ob_get_contents();
              ob_end_clean();

              if(ereg('gzip, deflate',$HTTP_ACCEPT_ENCODING)) {
              header ("Content-Encoding: gzip");
              echo "\x1f\x8b\x08\x00\x00\x00\x00\x00";
              $Size = strlen($contents);
              $Crc = crc32($contents);

              $contents = gzcompress($contents, 9);
              $contents = substr($contents, 0, strlen($contents) - 4);

              echo $contents;
              gzip_PrintFourChars($Crc);
              gzip_PrintFourChars($Size);

              } else {
              echo $contents;
              }

              function gzip_PrintFourChars($Val) {
                  for ($i = 0; $i < 4; $i ++) {
                      echo chr($Val % 256);
                      $Val = floor($Val / 256);
                  }
              }
              ?>

              Ich find's klasse.   :))

              Gruß, Horst.

              1. Moin

                Wenn sich die Möglichkeiten mit 2 Skripten nur auf die unten Beschriebene Methode mit den Inline-Bildern bezieht, kommt sie nicht in Frage, was bedeuten würde, ich hab im Moment keinerlei Perspektive mehr. :(((
                Wenn Du noch andere Möglichkeiten weißt, gib' mir bitte noch nen Schups.

                Also das mit den 2 Skripten geht eigentlich immer irgendwie. Hier ein allgemeines Beispiel (mit nur einem Skript)

                <?php

                $DIR="/tmp";  // Verzeichnis für temporäre Dateien, muss beschreibbar sein
                $GEHEIM="irgendEinString"; // Ein kleines Geheimnis, wird später verwendet

                if( !isset($ladeBild) ) {
                 // an dieser Stelle sollte das Skript dein externes Programm aufrufen und das machen, was sonst noch so nötig ist, um den HTML-Code drumherum auszugeben
                 // ich nehme jetzt einfach mal an, dass die Binärdaten des Bildes in $bild liegen

                // Wir generieren uns einen temporären Dateinamen
                 $tname = tempnam($DIR, "foo"); // Siehe http://www.php.net/manual/en/function.tempnam.php
                 if(!$tname) die("kann keinen Namen für die temporäre Datei finden");
                 $fh = fopen($tname, "w");
                 if(!$fh) die("kann die temporäre Datei nicht zum schreiben öffnen");
                 if(!fwrite($fh, $bild)) die("kann nicht in die temporäre Datei schreiben");
                 fclose($fh);

                // OK, wir haben jetzt die Bilddaten in einer temporären Datei und bereiten nun den erneuten Skriptaufruf vor
                 $imgsrc = $SCRIPT_NAME.'?ladeBild='.urlencode($tname).'&hash='.urlencode(md5($tname.$GEHEIM));

                /* in $imgsrc hast du jetzt den nötigen Code für den zweiten Aufruf, zum Beispiel mit
                    echo '<img src="'.$imgsrc.'">';
                    Du kannst jetzt also deinen umgebenden HTML-Code mit diesem eingebetteten Bild ausgeben
                 */
                } else {
                 if(get_magic_quotes_gpc()) $ladeBild = stripslashes($ladeBild);

                if(!md5($ladeBild.$GEHEIM) == $hash || !file_exists($ladeBild) ) {
                  header("HTTP/1.0 404 Not Found");
                  echo "Datei nicht gefunden"; exit();
                 }

                $fh = fopen($ladeBild, "r");
                 if(!$fh) die("Datei konnte nicht zum lesen geöffnet werden");
                 header("Content-type: image/jpeg");

                while (!feof($fh)) echo fread($fh, 1024);
                 fclose($fh);

                unlink($ladeBild);
                }
                ?>

                So oder so ähnlich würde ich das machen.
                Anmerkungen:

                • Das klappt nur einmal, danach ist die Datei gelöscht, wenn also jemand das HTML-Dokument neu lädt, sollte er unbedingt auch den Prozess auf dem Server neu anstoßen (also evt. das Cachen des HTML-Dokuments verbieten).
                • Wenn jemand mit Lynx surft oder Bilderladen abgestellt hat, sammeln sich mit der Zeit immer mehr temporäre Dateien an. Auf einem richtigen OS läuft zwar meist ein Cron-Job der das temp-Verzeichnis ab und zu aufräumt, aber du möchtest evt. noch selbst einen Garbage Collector einbauen, also alle auf diese Art generierten temporären Dateienamen irgendwo zentral sammeln und häufiger mal alte Dateien löschen.
                • Wenn jemand $GEHEIM errät, oder eine Lücke in MD5 finden sollte, dann hast du verloren und er kann beliebige Dateien auf dem Server lesen. Gegenmaßnahmen verbleiben als Übung für den interessierten Leser.
                • Der Code ist selbstverständlich ungetestet ;-)

                Wenn du sowieso PHP-Sessions verwendest (oder das immer schonmal tun wolltest), bieten sich auch diese an: Du speicherst einfach den Wert von $bild in der Session und schon kümmert sich PHP um alle Probleme die bei der Eigenbau-Lösung auftauchen: Jeder User kriegt nur sein Bild zu sehen; wenn die Session abläuft, kümmert sich der GC um die Datenreste; es kann niemand Dateien sehen, die nicht vom Skript erzeugt wurden.
                Als Kompromiss könnte man auch den Dateinamen der temporären Datei in der Session sichern.

                Noch eine Alternative geht in Richtung Inline-Image: Wenn dein Bild sowieso nicht allzu groß ist, reicht vielleicht ein Skript in der Form von
                <?php
                 header("Content-type: image/jpeg");
                 echo get_magic_quotes_gpc?$bilddaten:stripslashes($bilddaten);
                ?>
                Nachteil: Die Bilddaten gehen 3mal über die Leitung -> Blöde Idee

                Eine weitere Idee, wäre die bilderzeugende Aktion einfach mehrmals durchzuführen, so das überhaupt notwendig ist. Du gibst den HTML-Rahmen aus und gibst als Quelle für das Bild einfach nochmal das selbe oder ein ähnliches Skript an dem du alle Parameter die nötig sind, um das Bild zu erzeugen, mitgibst. Klappt natürlich nicht besonders gut, wenn diese Aktion mit irgendeinem Nebeneffekt verbunden ist oder 15 Sekunden Berechnungszeit benötigt.

                Such dir dein Gift aus :-)

                Es war ein hoffnungversprechender Ansatz, nur leider stört es mich ganz massiv, das der InternetExplorer zu den Browsern gehört, die die inlineBilder nicht Anzeigen können. Das ist nicht vertretbar.

                Also ich find's sehr praktisch. Der IE läuft hier eh nicht. *g*

                (vielleicht weißt Du das ja schon, und wolltest mich nicht unnötig verwirren)

                Ich hatte überlegt ob ich das noch erwähnen soll, mich dann aber doch dagegen entschieden da es für diesen Fall wohl nicht besonders wichtig ist.

                [snip]

                Diese OutPut-Kontrolle arbeitet so, das ALLES was mit echo, print() oder an HTML gesendet wird, in dem Puffer landet, und NICHT an den Browser gesendet wird. WOHL ABER werden alle Header() und
                setcookies() SOFORT an den Browser gesendet.

                afaik werden auch die header nach wie vor gepuffert, denn man kann die zu sendenden HTTP-Header mit header() immernoch überschreiben.

                [sehr viel nützliches, das jetzt im Archiv ist, gesnippt]

                Ich find's klasse.   :))

                Jo, ich mag es zum Beispiel damit dynamisch ein Inhaltsverzeichnis der Seite zu erstellen und das anschliessend _vor_ dem eigentlichen HTML-Code auszugeben.

                --
                Henryk Plötz
                Grüße von der Ostsee

                1. Moin Henryk,

                  bist Du schon wieder online!?!  [6:03]

                  Gestern warst Du auch von 5:24 bis (na laß mich mal schnell schauen)
                  ja genau mindestens bis 0:12 online. Hattest Du ein Mittagsschläfchen, oder kommst Du mit max 5 Stunden Schlaf Nachts aus? Oder mußt Du Momentan damit auskommen?  ;-)
                  ______

                  Also, meine Wahl ist eben rein intuitiv, (intelekt-mäßig hab' ich das eh' noch nicht verstanden, was Du da alles geschrieben hast) auf die Lösung mit den Sessions gefallen. Das scheint mir die einfachste und sauberste zu sein. Naja, eigentlich will ich nicht unbedingt Sessions verwenden, aber ich glaube, wenn man einigermaßen sinnvolles mit PHP im Web machen will, kommt man da nicht dranvorbei. Also, Ärmel hoch und los. (Dazu gibt's wenigstens ausreichend Infos.) 8-)
                  ______

                  Jo, ich mag es zum Beispiel damit dynamisch ein Inhaltsverzeichnis der Seite zu erstellen und das anschliessend _vor_ dem eigentlichen HTML-Code auszugeben.

                  Jooh, das klingt auch gut.

                  ______

                  Vielen Dank nochmal,
                  ..denke, ich bin jetzt erstmal ausreichend beschäftigt :-))

                  Grüße aus dem  Westzipfel,
                  Horst

                  1. Moin

                    bist Du schon wieder online!?!  [6:03]

                    Immer noch :)

                    Gestern warst Du auch von 5:24 bis (na laß mich mal schnell schauen)
                    ja genau mindestens bis 0:12 online. Hattest Du ein Mittagsschläfchen, oder kommst Du mit max 5 Stunden Schlaf Nachts aus? Oder mußt Du Momentan damit auskommen?  ;-)

                    Nein, schlafen tu ich in der momentan vorlesungsfreien Zeit eigentlich ziemlich lange. Von 6:00 bis 15:00 Uhr in etwa. ;-)

                    Also, meine Wahl ist eben rein intuitiv, (intelekt-mäßig hab' ich das eh' noch nicht verstanden, was Du da alles geschrieben hast) auf die Lösung mit den Sessions gefallen.

                    Ja, das ist eigentlich auch die sauberste Lösung, wenn du nicht grade ziemlich statische Daten hast. Wenn du zum Beispiel bloss eine Statistik für den vom User angeforderten Monat generieren willst, und sich die Daten seltener ändern, reicht es wohl auch aus, einfach eine Bilddatei a la "statistik_Mai_2000.jpeg" zu generieren und die dann da liegen zu lassen.

                    also ciao

                    --
                    Henryk Plötz
                    Grüße von der Ostsee