cr: fopen mit w, sicherheit?

hallo

ich öffne eine txt mit

file(test.txt); sowie
fopen("test.txt",w);
und sperre die datei dann sofort,
ändere den inhalt etwas ab und speichere ihn wieder.

sollte nun mal das speichern nicht funktionieren, wäre meine datei ja gelöscht (fopen mit w = 0bytes).

was kann ich tun um sicherzustellen, dass der inhalt nie verloren geht?

danke für eure hilfe und frohe ostern und viel spaß beim eier-suchen ;-)

  1. Hello,

    Eigentlich hatte ich das alles schon erklärt ...

    ich öffne eine Text-Datei mit

    file(test.txt);
    fopen("test.txt",w);

    Das ist gar nicht möglich.

    möglich, aber unsinnig wäre:

    $_file = file('test.txt');
      $fh = fopen('test.txt','w');     ## das leert die Datei

    Zwischen den beiden Statements entsteht ein Kontrollbruch.
    Es wird bei gut benutzten Systemen mit hoher Wahrscheinlichkeit ein anderer Prozess dazwischenfunken.

    und sperre die datei dann sofort,

    Das ist zu spät!

    ändere den inhalt etwas ab und speichere ihn wieder.

    Den Inhalt einer leeren Datei abzuändern ist schon sinnvoll *grins*

    sollte nun mal das speichern nicht funktionieren, wäre meine datei ja gelöscht (fopen mit w = 0bytes).

    Die muss jeden Fall als inkonsistent gelten!

    was kann ich tun um sicherzustellen, dass der inhalt nie verloren geht?

    Gar nicht.
    Atombomben sind stärker als Festplatten

    $fh = fopen($dateiname, 'a')     ## Datei zum Anhängen öffen oder anlegen
      if ($fh) fclose($fh);
      $fh = fopen($dateiname, 'r+');   ## vorhandene Datei zum Lesen und Schreiben öffnen
      $lock_ok = flock($fh, LOCK_EX);  ## Datei zum Zwecke des Lesens _und_ Schreibens sperren
                                       ## PHP wartet solange, bis es geklappt hat
      $file = fread($fh, filesize($dateiname)); ## Daten lesen

    # hier jetzt die Manipulationen vornehmen am Buffer, also $file

    fseek($fh, 0, SEEK_SET);         ## Dateizeiger zurück auf Anfang
      fwrite($fh, $file);              ## Veränderten Buffer zurückschreiben
      ftruncate($fh, strlen($file));   ## Dateilänge auf Bufferlänge kürzen
      fclose();                        ## Datei schließen und wieder freigeben

    Es gibt mit advisory Locking keinen anderen Weg, der sicher funktioniert!

    Diverse Spielarten der methode sind allerdings möglich
    -> Direktgestreute Datei
    -> Random Access File  (nur Satzsperre nötig)
    -> Baumstruktur

    Harzliche Grüße vom Berg
    http://www.annerschbarrich.de

    Tom

    --
    Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
    Nur selber lernen macht schlau

    1. Hello,

      $fh = fopen($dateiname, 'a')     ## Datei zum Anhängen öffen oder anlegen
        if ($fh) fclose($fh);
        $fh = fopen($dateiname, 'r+');   ## vorhandene Datei zum Lesen und Schreiben öffnen
        $lock_ok = flock($fh, LOCK_EX);  ## Datei zum Zwecke des Lesens _und_ Schreibens sperren
                                         ## PHP wartet solange, bis es geklappt hat
        $file = fread($fh, filesize($dateiname)); ## Daten lesen

      # hier jetzt die Manipulationen vornehmen am Buffer, also $file

      fseek($fh, 0, SEEK_SET);         ## Dateizeiger zurück auf Anfang
        fwrite($fh, $file);              ## Veränderten Buffer zurückschreiben
        ftruncate($fh, strlen($file));   ## Dateilänge auf Bufferlänge kürzen
        fclose();                        ## Datei schließen und wieder freigeben

      Es gibt mit advisory Locking keinen anderen Weg, der sicher funktioniert!

      Um das noch klarzustellen. Die Methode, mit Kopie und Backup ist selbstverständlich noch sicherer. Hier ging es erstmal nur um die Methodik des Sperrens!

      Harzliche Grüße vom Berg
      http://www.annerschbarrich.de

      Tom

      --
      Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
      Nur selber lernen macht schlau

      1. hallo,

        habe da noch eine frage:

        ich habe mal folgendes jetzt zusammengestellt und durch zufall oder k.a. warum macht php genau das, was ich will obwohl mein kopf sagt, dass es so nicht gehen kann!

        also:

        $legi = fopen("test.txt","r+");
        $legi_lock = flock($legi, LOCK_EX);
        $legiarray = file('text.txt');
        foreach($legiarray as $legiarrayzeile)
        {
        $legiarrayzeile = explode('|',$legiarrayzeile);
        $legiarrayzeile[3] = "neuer Inhalt";
        $legiarrayzeile = implode('|',$legiarrayzeile);
        fwrite($legi, $legiarrayzeile);
        }
        fclose($legi);

        Also ich möchte gern, dass php den inhalt eines bestimmten arrays ändert und die änderung dann in der datei gesichert wird, der zugriff während dieses vorgangs gesperrt ist, also keine dazwischenfunken kann.

        warum macht php das hier obwohl ich eigentlich sage, dass php am anfang der datei neu schreiben soll?

        danke für eure hilfe

        cr

      2. Guten Morgen,

        habe dein Script versucht an meine Bedürfnisse anzupassn und habe folgendes:

        $fh = fopen($dateiname, 'r+');
        $lock_ok = flock($fh, LOCK_EX);
        $file = fread($fh, filesize($dateiname));
        $file = explode("\n","$file");

        bis hierhin klappt es auch

        $file = explode("|","$file");

        aber jetzt exploded php nicht mehr,

        der inhalt der txt sieht so aus:

        eintrag1|eintrag2|eintrag3|
        eintrag11|eintrag22|eintrag33|   usw..

        ich möchte nun z.b. eintrag33 ersetzen, ohne dass jemand beim speichern dazwischenfunkt, was mache ich nun also mit dem 2. explode?

        danke für eure hilfe,

        cr

        1. Hello,

          $fh = fopen($dateiname, 'r+');
          $lock_ok = flock($fh, LOCK_EX);
          $file = fread($fh, filesize($dateiname));

          $_file = explode("\n",$file);

          bis hierhin klappt es auch

          Dann solltest Du Dir Deine Liste nicht kaputtmachen mit dem nächsten Statement

          $_zeile[$znr] = explode("|",$_file[$znr]);

          in $file ist die gesamte Datei als String gespeicht.
          in $_file ist dann nach dem explode die Datei als Array von Zeilen gespeichert
          in $_zeile wäre dann, wenn du es für alle Zeilen machst, das Array der Zeilen, die aufgeteilt in Felder sind, gespeichert.

          $_zeile = array();                  ## leeres Array bereitstellen

          foreach($_file as $key => $val)     ## jede Zeile in $_file bearbeiten
            {
              $_zeile[$key] = explode('|',rtrim($val));
            }

          Du kannst dann über

          echo $_zeile[$zeilennummer][$feldnummer];

          auf jedes (vorhandene) Feld der "Tabelle" zugreifen und es auch schreibend manupulieren.

          Um den Vorgang rückgängig zu machen, die Daten also wieder als im File ablegen zu können, verpackst Du erst die Zellen wieder

          foreach ($_file as $key => $_val)
            {
              $_file[$key] = implode('|',$_val);     ## aus den Teilarrays Strings machen
            }

          und verpackst dann das Array aus Zeilen wieder in einen String

          $file = implode("\n",$_file);            ## Zeilenumbruch nach jedem Teilstring

          $file kannst Du dann wieder in die Datei zurückschreiben.

          Zur Kenntlichmachung, ob der erwartete Wert ein Skalar oder ein Array ist, nutze ich immer den Unterstrich

          $file              sollte einen Skalar oder einen String enthalten
            $_file             sollte ein Array sein

          Das ist von mir frei, aber analog zu $_POST, $_GET, $_SERVER usw. gewählt und hat sich bewährt.
          Ich kann meine Scripte so viele leichter lesen :-)

          Schau Dir bitte auch

          http://www.php.net/manual/en/language.types.string.php

          an.

          Außerdem solltest Du beachten, dass bei diesem Verfahren in den Feldwerten bestimmte Zeichen nicht vorkommen dürfen. bei Dir sind dies:

          |       Feldtrenner
           "\n"     Zeilenendezeichen

          Das gibt sonst durcheinander

          Harzliche Grüße vom Berg
          http://www.annerschbarrich.de

          Tom

          --
          Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
          Nur selber lernen macht schlau

          1. Hallo Tom,

            irgendwie hänge ich immer noch...ich hab das was du geschrieben hast nun auf mein script umgesetzt, aber php speichert, wenn php überhaupt mal etwas speichert, nur komische vierecke in der txt, der inhalt geht jdesmal verloren.

            habe das script erstmal aufgeschlüsselt und mein fehler liegt hier bei explode und implode "|"

            explode mit "\n" und der implode funktioniert reibungslos...

            wenn man jetzt mal nur diesen teil betrachtet, würde meine anfängerlogik folgendes machen:

            foreach($filewelchesexplodedistmit\n as $zeile)
            {
            $zeile = explode("|", $zeile);
            if($zeile[0] == "hallo")
            {
            $zeile[1] = "bin drin";
            }
            }
            foreach($filewelchesexplodedistmit as $zeileimplode)
            {
            $filewelchesexplodedistmit = implode("|", $zeileimplode);
            }

            so darin ist sicher der fehler, ab ich finde ihn nicht...

            du?

            danke für deine hilfe!

            cr

            1. Hello,

              wenn man jetzt mal nur diesen teil betrachtet, würde meine anfängerlogik folgendes machen:

              foreach($filewelchesexplodedistmit\n as $zeile)

              Scheißnahmeverdammterdendudafuerdievariablegenommenhastbesonderswegendesbackslash  :-)

              {
                $zeile = explode("|", $zeile);
                if($zeile[0] == "hallo")
                {
                  $zeile[1] = "bin drin";
                }
              }

              foreach() ist etwas gewöhnungsbedürftig, aber sehr effektiv und schnell.

              # $_file ist als Array angelegt und belegt mit Daten.

              foreach($_file as $key => $zeile)
                {
                  $_file[$key] = explode('|',$zeile);

              if($_file[$key][0] == "hallo")
                  {
                    $_file[$key][1] = "bin drin";
                  }
                }

              $_file innerhalb der foreach()-Anweisung (Schleifenkonstrukt, _nicht_ Funktion) ist ein anderes $_file, als das noch vor der Schleife benutzte. Durch foreach() wird eine temporäre Kopie von $_file angelegt, mit der die Schliefe arbeitet.
              Die Schleifenargumente $key und $val (kannst Du nennen, wie Du willst: hier $key und $zeile) werden bei jedem Durchlauf der Schleife neu belegt, also überschrieben. Die letze "Füllung" steht nach der Schliefe noch zur Verfügung, was sehr nützlich sein kann.

              Wenn Du nun aber innerhalb der Schleife das durchlaufene ORIGINAL-Array $_file bearbeiten willst, dann musst Du das durch direkte Benutzung auch tun. So, wie ich es oben gezeigt habe, werden die Elemente des Original-Arrays bearbeitet. An die Kopie, die sich foreach() anlegt kommst Du selber nicht direkt heran, sondern immer nur an ihre momentanten Auszüge (ebenfalls nur Kopien) $key und $val.

              Das Ganze ist sehr sinnvoll, da man sich sonst leicht den Ast absägen könnte, auf dem man sitzt oder aber rekursive Verläufe schaffen würde. neure PHPs können auch mit einer Referenz in foreach() arbeiten, die Verwendung ist aber aus den erwähnten Gründen nur mit größter Aufmerksamkeit empfohlen.

              Wenn man es erst einmal verstanden hat, foreach zu verwenden, kann es eine der Lieblings"funktionen" werden.

              Harzliche Grüße vom Berg
              http://www.annerschbarrich.de

              Tom

              --
              Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
              Nur selber lernen macht schlau

              1. ahh....ich glaub ich habs...ich printe hier mal das, was jetzt bei mir in der original php datei steht, bitte entschuldige die variablen-bezeichnungen, aber sie erfüllen den zweck, dass ich nachvollziehen kann, was ich gemacht habe, diese werden später noch schöner und sauberer erscheinen...

                <?php
                if(isset($_POST['name']) && isset($_POST['email']))
                {
                 $neuername = $_POST['name'];
                 $neueemail = $_POST['email'];
                 $legitimation = fopen("files/legitimation.txt", "rb+");
                 $legitimation_lock = flock($legitimation, LOCK_EX);
                 $legitimation_daten = fread($legitimation, filesize('files/legitimation.txt'));
                 $legitimation_daten = explode("\n", $legitimation_daten);

                foreach($legitimation_daten as $key => $legitimation_daten_zeile)
                 {
                  $legitimation_daten[$key] = explode("|", $legitimation_daten_zeile);
                  if($legitimation_daten[$key][3] == $id)
                  {
                   $legitimation_daten[$key][6] = $neuername;
                  }
                 }
                 foreach($legitimation_daten as $key => $legitimation_daten_zeile)
                 {
                  $legitimation_daten[$key] = implode("|", $legitimation_daten_zeile);
                 }
                 $legitimation_daten = implode("\n", $legitimation_daten);
                 fseek($legitimation, 0, SEEK_SET);
                 fwrite($legitimation, $legitimation_daten);
                 ftruncate($legitimation, strlen($legitimation_daten));
                 fclose($legitimation);
                 $erfolg = 1;
                }
                ?>

                ist der code soweit i.o.? nach den ersten tests macht er zumindest das, was er soll.

                wenn ja dann schonmal vielen dank für deine große hilfe und geduld!

                grüße und schonen ostermontag

                cr

                1. Hello,

                  ist der code soweit i.o.? nach den ersten tests macht er zumindest das, was er soll.

                  In Bezug auf Lesen und Schreiben, explode und implode ist der Code erstmal in Ordnung.
                  In Bezug auf Sicherheit hat er noch schlimme Lücken.
                  In Bezug auf Modularisierung (Aufteilung in Funktionen mit festen Aufgaben) hat er auch noch Verbesserungspotential.

                  Erinnere Dich daran, dass man keinen Post-Daten oder sonstigen externen Quellen trauen darf. Wenn Dir jemand ein "Purple|Rain" in die Daten einschleust, dann ist Deine DB kaputt.

                  Ich will jetzt nicht weiter nachforschen, ob man Dein Zugriffssystem dadurch ausßer Gefecht setzen könnte...

                  Und dann würde ich aufteilen

                  - Übernahme von Post- und Get-Daten
                     nebst Entfernung von Backslashes (Magic Quotes)
                     Entschärfung und Validierung von Eingabfeldern usw.
                     Entferne also alle nicht erlaubten Zeichen und ersetze sie durch ein neutrales (z.B. Space)

                  - Einlesen und entpacken von Dateien

                  - Verarbeiten und Verändern der Datei

                  - Verpacken und Zurückschreiben der Datei.

                  wenn ja dann schonmal vielen dank für deine große hilfe und geduld!

                  Leider noch nicht ganz, aber sehr gerne geschehen!
                  Solange Du den Sinn der Diskussion für Dich erkennst, ist es OK.

                  Harzliche Grüße vom Berg
                  http://www.annerschbarrich.de

                  Tom

                  --
                  Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
                  Nur selber lernen macht schlau