Tom: Erkennen von Änderungen an Dateien

Beitrag lesen

Hello Christian,

entschuldige bitte, wenn das jetzt so aussah, als wollte ich Deinen Artikel runtermachen.
Der ist gut und wichtig. Das habe ich auch nicht bezweifelt.

Der Disput ist (auch hier) nur aufgekommen, weil ich unter

"eine Datei bearbeiten"

verstanden habe, dass cr den Inhalt ansehen will, um ihn zu editierern, also mit einer erheblichen Zeitlücke zwischen Lesen und Schreiben.

Wenn nur ein "update" an einem Datensatz über einen Schlüsselwert gemacht werden soll, und es nur wichtig ist, was hinterher im Datensatz drinsteht, ist das alles nicht nötig. Und dann verstehe ich Vinzenz' Einwand auch, dass man den Inhalt einer Datei zum Ändern nicht erst zum Client transportieren muss.

Mit den Methoden, die Christian Seiler beschreiben hat, kommst Du nicht zum Ziel. Dies insbesondere deshalb, weil er touch() benutzt. Das ist hier für eine einfache Lösung aber schädlich.

Hab ich das irgendwo so geschrieben? Kann ich jetzt nicht finden, will mich da auch nicht rausreden. Der ganze Knoten löst sich doch von selbst auf. Wir haben hier alle anneinander vorbei geredet.

Tut mir also wirklich leid.

Touch kann man beim zeitversetzten Lesen und Schreiben auf Dateien nicht benutzen, um eine vermeintlich nicht vorhandene Datei anzulegen, weil touch() sowohl die access-time als auch die modifiy-time ändert (ctime habe ich jetzt nicht überprüft), obwohl die Datei gar nicht verändert wird. Das hat für Touch sicher seinen Sinn, aber eben nicht für das vermutete Problem.

Damit würde man also ein generisch an die Datei gebundenes Kriterium zerstören, dass einem für das Erkennen einer zwischenzeitlichen Änderung zur Verfügung steht. Auf nichts anderes bezog sich mein Hinweis.

b) Deine Pauschalisierung "kommt man nicht zum Ziel" - natürlich kommt man damit nicht ALLEINE zum Ziel, aber File Locking an sich braucht man weiterhin, Dein Posting dagegen liest sich so, als ob mein Artikel hier völlig fehl am Platze ist.

vollkommen verkehrt verstanden. Bitte nochmals um Entschuldigung.

c) Das Dateimodifikationsdatum ist in meinen Augen ein sehr schlechtes Kriterium, weil es nur eine informative Angabe ist, die das Betriebsystem liefert, man sich auf sie jedoch bei kritischen Dingen NICHT darauf verlassen sollte und - wie Du selbst bemerktest - nur sekundengenau ist.

Die grobe Granularitätr würde mich auch abschrecken.
Aber wenn man sich auf das Betriebssystem nicht verlassen kann, worauf dann?

Eine viel sinnvollere Methode ist, mit Checksummen des Inhalts der Datei zu arbeiten (wenn sie sowieso komplett ausgelesen wird - warum nicht?).

Das ist eine gute Idee.
Wie sicher kann man sein, dass die bei größeren Dateien mit unterschiedlichem Inhalt auch noch unterschiedlich sind?

Folgendes Vorgehen wäre für diesen Fall am sinnvollsten (nochmal alles zusammengefasst, halt mit Prüfsummen statt Modifikationsdatum):

  1. Beim Anzeigen des Formulars wird die gesamte Datei eingelesen, dabei auch Locking betrieben, um parallelen Schreibzugriffen einen Riegel vorzuschieben (GANZ WICHTIG: Fehlerbehandlung ist hier noch nachzutragen! Der Code hier unten geht der Einfachheit wegen davon aus, dass die Funktionen immer erfolgreich sind!):

$datei = 'datei.txt'; // oder sonstwoher

$fd = fopen ($datei, 'rb');
flock ($fp, LOCK_SH);

$inhalt = fread ($fp, filesize ($datei));  ## Achtung, wenn sie leer ist ;-)

fclose ($fp);

  
  

> 1a. Danach wird eine Prüfsumme des Inhalts berechnet. Nachdem es hier nicht um kryptographische Sicherheit geht, sondern nur um Konsistenzprüfung, dürfte MD5 vollkommen ausreichend sein:  
  
  

> `$pruefsumme = md5 ($inhalt);`  
>   
> 1b. Nun wird der Inhalt zeilenweise aufgespalten:  
>   
> `$zeilen = preg_split ("/\r\n|\n|\r/", $inhalt);`  
>   
> Danach enthält $zeilen ein Array mit allen Zeilen der Datei - allerdings OHNE die Zeilentrennzeichen - das ist der Unterschied zu file().  
  
...was ja für die Weiterverarbeitung auch besser ist, weil sonst immer das letzte Element des Zeilenarrays noch mit rtrim() behandelt werden muss. Die könnten bei PHP ruhig mal einen optionalen Parameter einführen, der den Zeilenumbruch bei file() automatisch entfernt. Er gehört nicht zu den Daten.  
  

> 1c. Nun wird das Formular erzeugt, dort gibt es zum einen die Felder, die sich an Hand von $zeilen ergeben (wie Du's halt bisher erzeugst). ZUSÄTZLICH gibt es ein Hidden-Feld `<input type="hidden" name="pruefsumme" value="...">`, wobei das '...' durch den Inhalt der Variable $pruefsumme zu ersetzen ist.  
>   
  

> 2. Wenn das Formular ankommt, wird es zuerst validiert, damit die Benutzeringaben gültig sind. Wenn nicht, bekommt es der User wieder vorgesetzt. Ist dies geschehen, wird die Datei wieder geöffnet (diesmal zum Lesen & Schreiben), die Prüfsumme erneut generiert und dann der Dateizeiger zurückgesetzt (wir gehen davon aus, dass die Datei in der Zwischenzeit NICHT gelöscht wurde, auch hier wieder: FEHLERBEHANDLUNG SELBST NACHTRAGEN!):  
>   
> ~~~

$datei = 'datei.txt'; // oder sonstwoher  

> $fd = fopen ($datei, 'rb+'); // zum lesen UND schreiben  
> flock ($fp, LOCK_EX); // EXKLUSIVE sperre  

  $inhalt = fread ($fp, filesize ($datei));   ## wieder beachten, dass Länge > 0 sein muss  

> $pruefsumme = md5 ($inhalt);  
> fseek ($fp, 0, SEEK_SET);  
> // DATEI NICHT SCHLIESSEN!

Nun wird $pruefsumme mit der über das Formular übermittelten Prüfsumme verglichen ($pruefsumme == $_POST['pruefsumme']). Wenn sie übereinstimmen, wurde die Datei NICHT verändert seit dem Anzeigen des Formulars, dann kann der Inhalt problemlos zurückgeschrieben werden. Dies geschieht, indem die Datei "zurechtgestutzt" wird und danach ganz Normal fwrite() genutzt wird:

if ($pruefsumme == $_POST['pruefsumme']) {

ftruncate ($fp, 0);
  fwrite ($fp, $neuerInhalt);
  fclose ($fp);
} else {
  fclose ($fp);
  // siehe unten...
}


>   
> Wie Du an $neuerInhalt kommst, bleibt Dir (= dem mit dem Problem) überlassen. Du kannst an dieser Stelle zum Beispiel den alten Inhalt wieder zeilenweise aufteilen und die POST-Daten an die entsprechenden Zeilen einfügen und dann per `join ("\n", $zeilen)` die Zeilen wieder zu einem String zusammenfügen oder Du kannst nur aus den POST-Daten den neuen Inhalt erstellen - das bleibt ganz Dir überlassen. Sobald die Datei geschlossen ist, war alles erfolgreich und Du kannst Dich zurücklehnen.  
>   
> Nun zur else-Bedingung, die auftritt, wenn die Prüfsumme NICHT übereinstimmt. Dann ist folgender Fall eingetreten: Jemand hat die Datei modifiziert, NACHDEM Du das Formular an den Client ausgeliefert hast, BEVOR es jedoch zurückgekommen ist. Je nachdem wie komfortabel es für den Benutzer machen willst, kannst Du Dir überlegen, wie Du darauf reagierst. Du könntest eine Fehlermeldung ausgeben. Du könntest die Unterschiede zwischen den verschiedenen Versionen anzeigen. Und so weiter, und so fort.  
  
  
  
  
  
  
  
Harzliche Grüße vom Berg  
<http://bergpost.annerschbarrich.de>  
  
Tom

-- 
Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen  
Nur selber lernen macht schlau  
Ein Jammer ist auch, dass die Dummen so selbstsicher und die Klugen voller Zweifel sind. Das sollte uns häufiger zweifeln lassen :-)  
  
![](http://bitworks.de/~selfHTML/Virencheck.gif)