Enrico: "is_writable" schlägt fehl

Hallo,

ich hoffe nicht, dass ich mit meinem Problem ein altes Problem wieder aufwerfe.

Ich will den Inhalt einer Textdatei mit folgender Funktion neu schreiben:

  
   function DateiSchreiben ($Datei, $Inhalt)  
   {  
      if (file_exists ($Datei))  
      {  
         if ($Zeiger = fopen ($Datei, 'w'))  
         {  
            if (flock ($Zeiger, LOCK_SH))  
            {  
               if (is_writable ($Datei))  
               {  
                  ftruncate ($Zeiger, 0);  
  
                  if (fwrite ($Zeiger, $Inhalt))  
                  {  
                     fclose ($Zeiger);  
  
                     if (flock ($Zeiger, LOCK_UN))  
                     {  
                        return 'ok';  
                     }  
                     else  
                        return 'Die Datei "' . $Datei . '" konnte nicht wieder freigegeben werden';  
                  }  
                  else  
                     return 'Die Datei "' . $Datei . '" konnte nicht geschrieben werden';  
               }  
               else  
                  return 'Die Datei "' . $Datei . '" ist nicht beschreibbar';  
            }  
            else  
               return 'Die Datei "' . $Datei . '" konnte nicht gesperrt werden';  
         }  
         else  
            return 'Die Datei "' . $Datei . '" konnte nicht zum Schreiben geöffnet werden';  
      }  
      else  
         return 'Die Datei "' . $Datei . '" konnte nicht gefunden werden';  
   }  

Aufrufen tue ich die Funktion mit $Meldung = DateiSchreiben ('../TEXTDATEIEN/Startseite.txt', $Inhalt);

file_exists, fopen und flock funktionieren einwandfrei, jedoch schlägt is_writable fehl.

Muss ich die Textdatei vielleicht noch mit besonderen Rechten ausstatten?
Wenn ja, geht das mittels PHP?

Wenn nicht, woran kann es liegen, dass is_writable fehlgeschlagen ist?

Danke für eure Hilfe.

Gruß
Enrico

  1. if ($Zeiger = fopen ($Datei, 'w'))
           {
                if (flock ($Zeiger, LOCK_SH))
                {
                   if (is_writable ($Datei))

    Hm. Eigentlich sollte schon das fopen zu einer Warnung führen, wenn die Rechte nicht ausreichend sind. Bei mir:

    PHP Warning:  fopen(test.txt): failed to open stream: Permission denied in /home/fastix/test.php on line 3

    Danach schlägt auch das flock fehl, denn $Zeiger enthält jetzt false:

    PHP Warning:  flock() expects parameter 1 to be resource, boolean given in /home/fastix/test.php on line 4

    Deine Reihenfolge für die Tests ist also alles andere als sinnvoll. Prüfe ob die Datei existiert dann ob diese schreibbar ist.

    Dann versuche fopen und wenn dieses funktionierte flock.

    Zu Deiner Frage: Betriebssystem?

    Falls Linux: Was sagt ein

    print php ls -l $Datei."\n";
    Oder ein ls -l ../TEXTDATEIEN/Startseite.txt mit ssh ausgeführt?

    Was sagt:
    print php ls -dl $Datei."\n";
    Oder ein ls -dl ../TEXTDATEIEN mit ssh ausgeführt?

    Was steht in Deiner php.ini?

    (Ausgaben mit <?php phpinfo(); ?>

    Im Handbuch steht zu safe_mode und fopen

    "Überprüft, ob das Verzeichnis, in dem das Skript ausgeführt werden soll, die gleiche UID (Eigentümer) hat wie das Skript selbst"

    und zu All filesystem and stream functions: "Überprüft ob die Dateien/Verzeichnisse, die mit dem Skript bearbeitet werden sollen, die gleiche UID (Eigentümer) haben wie das Skript selbst. Überprüft, ob das Verzeichnis, in dem das Skript ausgeführt werden soll, die gleiche UID (Eigentümer) hat wie das Skript selbst. (see the safe_mode_include_dir php.ini option."

    Wer ist also Eigentümer der Datei und als welcher User agiert PHP?

    Jörg Reinholz

    1. Hallo Jörg,

      vielen lieben Dank für Deine ausführliche Antwort.

      Ich habe zunächst mein Konstrukt gemäß Deinem Ratschlag umgestellt, leider aber auch ohne Erfolg.

      Ich dachte nicht, dass man auf so viele Dinge achten muss (Betriebssystem, Rechte, Unterscheidung nach Dateieigentümer und Nutzer in Verbindung mit der Rolle von PHP in diesem Zusammenhang,...).

      Dann habe ich file_put_contents, wie es dedlfix vorgeschlagen hat, verwendet und dies hat problemlos geklappt.

      Gruß
      Enrico

      1. Ich dachte nicht, dass man auf so viele Dinge achten muss (Betriebssystem, Rechte, Unterscheidung nach Dateieigentümer und Nutzer in Verbindung mit der Rolle von PHP in diesem Zusammenhang,...).

        Das hängt im konkreten Fall mit der Fehlermeldung zusammen, die darauf hindeutete, dass womöglich(!) ein Problem mit den Dateirechten vorlag. Die sind nun mal betriebssystemspezifisch.

        Ich habe zunächst mein Konstrukt gemäß Deinem Ratschlag umgestellt,

        Der Ratschlag bezog sich darauf, dass Deine "Prüfung" mit is_writable() schlicht und einfach zu einem Zeitpunkt erfolgte, an dem das Kind bereits im Brunnen lag, nämlich nach dem fopen() - und ergo nicht sinnvoll ist. Für eine bessere Fehlermeldung kannst Du das is_writable() auch vor file_put_contents() setzen. Allerdings hast Du dann immer noch das Problem, dass (theorethisch) ein paralleler Prozess zwischen Deiner Prüfung und dem eigentlichen schreibenden Zugriff auf die Datei zugreift und diese blockiert - du musst also immer noch prüfen ob file_put_contents() funktionierte.

        Noch ein Tipp: Benutze das error_reporting, denn dann hättest Du das auch gesehen. Etwas wie ein:

        if ('127.0.0.1'==$_SERVER['REMOTE_ADDR']) {  
           error_reporting(E_ALL);  
        } else {  
           error_reporting(0);  
        }
        

        könnte dabei helfen, zu unterscheiden ob Dein Skript auf einem Testsystem läuft (der Client ist im konkreten Beispiel der Browser auf dem Localhost) oder auf einen produktiven Server, auf dem das Präsentieren von Fehlern ggf. unerwünscht ist.

        Du hast offenbar Defaulteinstellungen benutzt die auch Warnungen ausschließen oder gibts keine Fehler im Dokument aus UND Du hast nicht in die Error-Logs geschaut.

        Du solltest Dir beim Entwickeln etwas angewöhnen wie z.B. tail -f /var/log/apache2/error\_log in einer Shell auszuführen, damit Du solche Fehler, Warnungen und Notizen gegebenenfalls sofort siehst, wenn Du das Skript zumm Testen via Browser und Webserver ausgeführt hast.

        Auf Linux-Systemen ist das eine sehr gute Methode um PHP zu debuggen. Bei mir läuft das in einem Fenster von Kate (das benutze ich als Editor) mit.

        Unter Windows 7 gibt es meines Wissens auch ein Programm zum (Live-)Beobachten von Logfiles, verzeih mir, dass ich dessen Name ich jetzt nicht kenne.

        Jörg Reinholz

  2. Tach!

    Ich will den Inhalt einer Textdatei mit folgender Funktion neu schreiben:

    Warum nimmst du nicht file_put_contents()?

    if ($Zeiger = fopen ($Datei, 'w')) {
                if (flock ($Zeiger, LOCK_SH)) {
                   if (is_writable ($Datei))

    Das ganze Konstrukt ist übertrieben vorsichtig. Wenn die Datei zum Schreiben geöffnet werden kann, dann ist sie schreibbar. Es besteht kein Grund, daran zu zweifeln, wenn keine Fehlermeldung kommt. Der Grund warum is_writable() fehlschlägt, ist der Mix von handle- und namensbasierenden Funktionen. Nachdem ein Handle erzeugt wurde und über dieses Handle die Datei gesperrt wurde, versuchst du mit einer namensbasierten Funktion weiterzuarbeiten. Die weiß von dem Handle nichts und versucht die Datei zum Schreiben zu öffnen, was wegen der Sperre fehlschlägt.

    dedlfix.

    1. Hallo dedlfix,

      danke für Deine Antwort.

      Mit file_put_contents hat es nun problemlos geklappt.

      Welche Mechanismen zur Absicherung der Zugriffe auf die Datei würdest Du empfehlen?
      In der gleichen Reihenfolge, wie sie Jörg vorgeschlagen hat?

      Was kann ich "gegen" gleichzeitige Zugriffe machen?

      Zur Erklärung:

      Es handelt sich bei meinem Vorhaben um die Startseite, auf der ich Neuigkeiten ausgebe und unseren Besuchern die Möglichkeit geben möchte, Kommentare zu den Neuigkeiten zu hinterlassen, ähnlich einem Forum.

      Gruß
      Enrico

      1. Tach!

        Mit file_put_contents hat es nun problemlos geklappt.
        Welche Mechanismen zur Absicherung der Zugriffe auf die Datei würdest Du empfehlen?

        Nur file_put_contents(), das macht alles in einem Rutsch, ohne dass du die Möglichkeit hast, zu viele Fehler einzubauen. Das Flag LOCK_EX würde ich noch verwenden, aber ich kann nicht sagen, ob damit Nebenwirkungen verbunden sind. Das kommt immer auf das Verhalten der anderen Funktionen an. Zum Lesen gibts ja das Pendant file_get_contents(). Das macht seine Sache ebenfalls in einem Vorgang. Dessen Verhalten, wenn es auf eine gerade von file_put_contents() gesperrte Datei trifft, kenne ich aber auch nicht. Und es dürfte schwierig sein, das im Labor genau zu untersuchen. Natürlich kann man die Implementation der beiden Funktionen im Code von PHP anschauen ...

        Als pragmatischen Ansatz würde ich in beiden Fällen zunächst einen Aufruf-Versuch unternehmen. Wenn der fehlschlägt, eine Zeitlang sleep()en (eine halbe Sekunde wird sicher mehr als ausreichend sein) und den Vorgang wiederholen. Ist auch der erfolglos, dann abbrechen.

        Es handelt sich bei meinem Vorhaben um die Startseite, auf der ich Neuigkeiten ausgebe und unseren Besuchern die Möglichkeit geben möchte, Kommentare zu den Neuigkeiten zu hinterlassen, ähnlich einem Forum.

        Dann würde ich SQLite in Erwägung ziehen. Das kümmert sich selbst um den problemfreien Datenzugriff.

        dedlfix.

        1. Hallo dedlfix,

          Nur file_put_contents(), das macht alles in einem Rutsch
          Zum Lesen gibts ja das Pendant file_get_contents(). Das macht seine Sache ebenfalls in einem Vorgang

          Ja, diesen Befehl habe ich mir dann auch gleich noch angeschaut.

          Da meine DateiEinlesen-Funktion ebenfalls sehr umfangreich ist, werde ich diese auch entschlacken.

          Danke Dir für Deine Hilfe :-)

          Schönen Abend noch und Gruß
          Enrico

  3. Hi Enrico,

    ACHTUNG: Du machst hier einen fatalen Fehler!

    Wie dedlfix schon gesagt hat, würdest du am besten einfach file_put_contents() verwenden, der Vollständigkeit halber und fürs Archiv, möchte ich deinen Code aber trotzdem noch kommentieren.

    if ($Zeiger = fopen ($Datei, 'w'))
             {
                if (flock ($Zeiger, LOCK_SH))
                {
                   if (is_writable ($Datei))
                   {
                      ftruncate ($Zeiger, 0);

      
    Durch das Öffnen der Datei mit [fopen()](http://www.php.net/manual/en/function.fopen.php) im Modus "w" wird die Datei direkt beim Öffnen "geleert", der gesamte Inhalt der Datei wird gelöscht - damit hast du die Datei zerstört. Dass du dann im Anschluss noch versucht einen Schreib-Lock zu erwirken ist überflüssig, weil du dann ja längst an der Datei herumgearbeitet hast und das ftruncate() ist nutzlos, weil die Datei dann eh schon längst die Größe 0 hat.  
      
    Korrekterweise solltest du die Datei im Modus "r+" Öffnen, dann den Schreib-Lock erwirken und dann ftruncate($fp, 0) machen.  
      
    Die Modi "w" und "w+" sollte man - wenn auch nur irgendwie ein bisschen Wert auf simultanen Dateizugriff legt - grundsätzlich NIE verwenden. Seit PHP 5.2.6 wurden deshalb die Modi "c" und "c+" eingeführt, welche sich im Prinzip wie "w" und "w+" verhalten, jedoch die Datei nicht leeren.  
      
      
    Viele Grüße,  
      ~ Dennis.
    
    -- 
    Mein [SelfCode](http://community.de.selfhtml.org/fanprojekte/selfcode.htm): [ie:{ fl:( br:> va:) ls:\[ fo:) rl:( n4:# ss:) de:\] js:| ch:{ sh:| mo:} zu:|](http://www.peter.in-berlin.de/projekte/selfcode/?code=ie%3A%7B+fl%3A%28+br%3A%3E+va%3A%29+ls%3A%5B+fo%3A%29+rl%3A%28+n4%3A%23+ss%3A%29+de%3A%5D+js%3A%7C+ch%3A%7B+sh%3A%7C+mo%3A%7D+zu%3A%7C)