Kaputte Dateien im Filesystem ausfindig machen

Winfried Mueller, www.reintechnisch.de, Start: 20.05.2007, Stand: 20.05.2007

Der Supergau eines jeden Adminstrators ist eingetroffen! Zufällig fiel mir auf, dass eine Datei auf einem Server einen fehlerhaften Inhalt hatte. Sie war anders, als im Backup, was vor 3 Monaten gemacht wurde. Aber: Sie wurde in dieser Zeit nicht verändert, der Zeitstempel der letzen Änderung lag bereits 1 Jahr zurück. Das trieb mir den Schweiß auf die Stirn, weil das ja ein direktes Indiz dafür ist, dass es ein Hardwareproblem gibt und uns schleichend unbemerkt Dateien kaputt gehen.

Das war dann der Grund, ein Testprogramm zu schreiben. Zuerst einmal spiele ich ein Backup in eine temporäres Verzeichnis zurück. Alle Dateien in diesem Backup werden nun mit den Originalen verglichen. Dateien, die sich seither regulär verändert haben, tragen einen anderen Zeitstempel und werden nicht geprüft. Dateien hingegen, die den gleichen Zeitstempel tragen, müssen auch vom Inhalt her gleich sein. Und genau das soll mein Programm testen.

Jede Datei, die also einen gleichen Zeitstempel hat, wird zuerst mal in der Länge gecheckt. Sind die Längen schon unterschiedlich, wird ein Längen-Fehler ausgespuckt. Sind die Längen identisch, wird eine MD5-Prüfsumme gebildet, was einem Bitvergleich beider Dateien gleichkommt. Stimmen nun beide Dateien nicht überein, wird ein MD5SUM-Fehler ausgegeben.

Zum Schluß wirft das Programm noch Statistiken raus, wieviel Dateien verarbeitet wurden und wieviel Fehler auftraten.

Das Programm ist ein Schnell-Hack, was aus der Not heraus mal eben entstand. Getestet unter Ubuntu Linux Dapper.

Wer testen will, ob das Programm richtig funktioniert, verändere eine Datei, die sowohl im Backup, wie im Original identisch war. Einmal verändert man ohne Längendifferenz, einmal mit Längendifferenz. Gut ist dafür ein Textfile geeignet. Nach der Veränderung macht man ein touch, um die mtime des Backup-Files zu übernehmen:

 
# touch --reference=/reference/to/backup/file originalfile

Jetzt haben beide die gleiche mtime, jedoch unterschiedlichen Inhalt. Das Programm sollte einen Fehler ausspucken.

Übrigens: Wer mit rsync Verzeichnisse synchronisiert, synchronisiert nicht in dieser Form fehlerhafte Dateien mit. Der Grund ist, dass rsync keinen Bitvergleich macht sondern sich an die mtime hält. Wurde also einmal synchronisiert und in der Zwischenzeit ist auf dem Quellverzeichnis eine Datei kaputt gegangen, sollte sie im rsync-Zielverzeichnis ganz erhalten bleiben. Auch nach erneuten Synchronisationen.

Quellcode

 
# fccheck - Teste Übereinstimmung von Dateien zweier Verzeichnisse,
#           insofern Datei gleiche mtime hat aber unterschiedlichen Inhalt
#           Ideal zum Testen von Plattendefekten im Vergleich mit Backups
# Use:
#   fccheck dir1 dir2
#     dir1 - Referenzverzeichnis, alle die Files werden überprüft, typischerweise
#            das Backup
#     dir2 - Verzeichnis, mit dem dir1 verglichen wird, typischerweise
#            das Original
#
# Winfried Mueller, www.reintechnisch.de, Start: 20.05.2007, Stand: 20.05.2007
#
#

require "find"
require 'digest/md5'

dir1 = ARGV[0]
if !File.directory?( dir1 )
  puts "dir1 not found."
  raise
end
dir2 = ARGV[1]
if !File.directory?( dir1 )
  puts "dir2 not found."
  raise
end

# md5sum einer Datei berechnen
# 
def getmd5sum( file )
  result = nil
  if File.file?( file )
    dr = Digest::MD5.new
    begin
      File.open( file, "rb" ) do |f|
        buf = ""
        while !f.eof?
          f.read( 102400, buf )
          dr << buf
        end
      end
      result = dr
    rescue
      #kein Fehler produzieren
    end
  end
  return result
end

stat_files = 0
stat_files_both = 0
stat_ferr_md5sum  = 0
stat_ferr_size = 0
stat_fsame_size = 0
stat_fsame_mtime = 0

Find.find( dir1 ) do |file|
  next if !File.file?( file )
  stat_files += 1
  rel_fn = file[dir1.length+1..-1]
  #puts rel_fn
  fn2 = File.join( dir2, rel_fn )
  if File.file?( fn2 )
    stat_files_both += 1
    same_size = false
    same_time = false
    if File.stat( file ).size == File.stat( fn2 ).size
      stat_fsame_size += 1
      same_size = true
    end
    if File.stat( file ).mtime == File.stat( fn2 ).mtime
      stat_fsame_mtime += 1
      same_time = true
    end      
    if same_time
      puts "Check File: #{rel_fn}"
      if !same_size
        puts "  Defect size **********************"
        stat_ferr_size += 1
      elsif getmd5sum( file ) != getmd5sum( fn2 )
        puts "  Defect md5sum ********************"
        stat_ferr_md5sum += 1
      else
        puts "  -> OK"
      end
    end
  end
end

puts "Statistik"
puts "Files on dir1: #{stat_files.to_s}"
puts "Files found in dir1 and dir2: #{stat_files_both.to_s}"
puts "Defect md5sum Files: #{stat_ferr_md5sum.to_s}"
puts "Defect size Files: #{stat_ferr_size.to_s}"
puts "Files with same size: #{stat_fsame_size}"
puts "Files with same mtime: #{stat_fsame_mtime}"

Typische Konsolenausgabe

 
fccheck /temp/bktest/home/data /home/data
Check File: var/thunderbird/sopha/Unsent Messages
  -> OK
...

Check File: test.txt
  Defect md5sum ********************
...

Statistik
Files on dir1: 13132
Files found in dir1 and dir2: 12501
Defect md5sum Files: 1
Defect size Files: 0
Files with same size: 12100
Files with same mtime: 11658


Des Rätsels Lösung

Grund für dieses Werkzeug war ja die Beobachtung, dass Dateien sich veränderten, obwohl die lt. Datumsstempel nicht verändert wurden. Nachdem ich mit fccheck diverse Test durchgeführt hatte, stellte ich fest, dass ausschließlich Excel-Dateien betroffen waren. Weil die Platte aber wohlgemischt alles enthielt, konnte ich schonmal sicher sein, dass kein Hardwaredefekt dafür verantwortlich sein kann. Die Hardware hat ja auf dieser Ebene keine Information darüber, was Excel-Dateien sind, auf dieser Ebene gibt es nur Bits und Bytes.

Es stellte sich heraus, dass Excel bei jedem Öffnen einer Datei den Inhalt in ein paar Bytes ändert, ohne jedoch die Modifikation-Time zu verändern. Da wird also was modifiziert, dies aber vor dem Benutzer verborgen. Zumindest ist das bei Excel 97 der Fall. Und anscheinend auch bei Powerpoint. Bei Word-Dokumenten ist es nicht so.

Das ist natürlich ziemlich unschön bzw. konzeptionell eher schlecht. Damit funktioniert bei Excel-Dateien fccheck nicht korrekt. Hier ist es normal, dass bei gleicher mtime sich der Inhalt ändern kann. Was sich jedoch ändert, wenn die Datei auf einem Samba-Linux-Share liegt, ist die ctime. Die atime hingegen bleibt ebenso unverändert.