Kommandozeilen Werkzeug md5check
Winfried Mueller, www.reintechnisch.de, Start: 26.12.2006, Stand: 26.12.2006
Die Dateikonistenz ganzer Verzeichnisbäume zu überprüfen ist die Aufgabe dieses Werkzeugs. Das ist z.B. praktisch, wenn man ein Verzeichnis woanders hinkopiert hat und nochmal auf "Nummer sicher" gehen will, ob wirklich jede Datei Byte für Byte korrekt kopiert wurde. Auch für die Datensicherung kann dies ein sehr hilfreiches Werkzeug sein. Oder, um Veränderungen an Dateien aufzuspüren, wo sich eigentlich nichts verändern darf.
Das Werkzeug funktioniert ähnlich, wie das unter Unix bekannte md5sum, ist jedoch auf Verzeichnisbäume ausgelegt und funktioniert plattformübergreifend. Mit dem Befehl:
md5check -l /home/klaus > klaus.lst
erzeugt man eine Liste namens klaus.lst, in der alle Dateinamen unterhalb des Verzeichnisses /home/klaus inkl. der dazugehörigen md5sum abgelegt werden. Hierzu wird jede Datei Byte für Byte eingelesen und die md5sum berechnet. Natürlich braucht dies entsprechend Zeit. Trotzdem ist Ruby hier recht fix.
Diese Liste kann man nun später für eine Überprüfung benutzen. Nehmen wir an, wir haben das Verzeichnis /home/klaus nach /home/test/klaus kopiert. Dann führt folgender Befehl zur Überprüfung mit der zuvor erstellten Liste:
md5check -c klaus.lst /home/test/klaus
Bei einer Datensicherung kann man das gut nutzen, in dem man vor der Sicherung von jeder Datei die md5sum berechnen lässt und die erzeugte Liste ebenso mit sichert. So kann man später genau erkennen, wo Dateien evtl. defekt sind. Datenträger können so von Zeit zu Zeit automatisiert überprüft werden.
Oder man vergleicht die aktuellen Dateien in einem Verzeichnis mit einer älteren Liste, um die Veränderungen im Verzeichnisbaum zu erkennen. Für Programmverzeichnisse kann man so sehr gut unerlaubte Veränderungen entdecken, z.B. bei Virenbefall. Man könnte z.B. das Verzeichnis c:\winnt überprüfen, wo sich ja alle ausführbaren Programme nicht verändern sollten, solange man keine neue Software einspielt oder Updates macht.
Das Programm war ein Schnell-Hack und ist grob unter Windows 2000 und Ubuntu Linux mit Ruby 1.8.x getestet. Wenn man bedenkt, dass jedes Byte einer jeden Datei gelesen werden muss, ist die Geschwindigkeit des Werkzeugs beeindruckend. Das liegt vor allem an der groß gewählten Blockgröße und das digest/md5 ein C-Modul ist.
Quellcode
#!/usr/bin/ruby1.8 # md5check - Erzeuge und teste md5checksum-Liste # Winfried Mueller, www.reintechnisch.de, (c) 2006, GPL # Start: 26.12.2006, Stand: 26.12.2006 # # Generate List # md5check -l /home/klaus > klaus.lst # # Check List # md5check -c klaus.lst /home/klaus # DEBUG = true Version = "2006/12/26" require 'find' require 'optparse' require 'ostruct' require 'digest/md5' # 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 # md5 Checksum-Liste erzeugen, beginnend bei basedir rekursiv # Ausgabe auf stdout # def makemd5list( basedir ) Find.find( basedir ) do |path| next if File.directory?(path) spath = path.sub( /^#{basedir}\/?/, "") next if spath.length == 0 #puts path digest = getmd5sum(path) if !digest $stderr.puts "File not accessible: #{path}" else printf "%s %s\n", digest.hexdigest, spath end end end # Checksum-Liste mit Verzeichnis prüfen # def checkmd5list( list, basedir, verbose=false ) line_nr = 1 File.foreach( list ) do |line| if line =~ /^([0-9a-f]+)\s+(.*)$/ lchecksum = $1 lfname = $2.chomp #puts lchecksum+ "----"+lfname else raise( "List Format error line: #{line_nr}" ) end dstfile = File.join( basedir, lfname ) if !File.file?(dstfile) printf( "%s -- not found\n", lfname ) else digest = getmd5sum( dstfile ) if !digest printf( "%s -- not accessible.\n", lfname ) elsif digest.hexdigest != lchecksum printf( "%s -- different\n", lfname ) else if verbose printf( "%s -- ok\n", lfname ) end end end line_nr += 1 end end # ---------------------------------------------------------------- ARGV.options do |o| o.banner = "Usage: md5check [options] basedir" o.separator( "md5check Check md5sum for a filelist or make a filelist." ) o.separator( "" ) o.on( "-l", "--mklist", "Make a md5sum Filelist recursive from basedir." ) do $options.mklist = true end o.on( "-c", "--check FILELIST", "Check Filelist with basedir." ) do |f| $options.check = f end o.on( "-v", "--verbose", "All Check results (default only different Files)." ) do $options.verbose = true end o.on_tail("-h", "--help", "This help." ) do puts o exit end o.on_tail("--version", "Show version") do puts o.ver puts "Written by Winfried Mueller, www.reintechnisch.de" puts "" puts "Copyright (C) 2006 Winfried Mueller" puts "This is free software; see the source for copying conditions." puts "There is NO warranty; not even for MERCHANTABILITY or" puts "FITNESS FOR A PARTICULAR PURPOSE." exit end end # ---------------------------------------------------------------- begin $options = OpenStruct.new $options.verbose = false err_code = 0 ARGV.parse! if ARGV.length == 0 # Help if no Arguments STDERR.puts ARGV.options.to_s else # process fn_config = ARGV unless fn_config != nil #puts fn_config raise( "No basedir specified." ) end basedir = fn_config[0].dup basedir.gsub!( /\\/, "/" ) #Trenner für Pfad auf / anstatt \ umschreiben if !File.directory?( basedir ) raise( "basedir not found." ) end if $options.mklist != nil makemd5list( basedir ) elsif $options.check != nil checkmd5list( $options.check, basedir, $options.verbose ) else STDERR.puts ARGV.options.to_s end end rescue SystemExit rescue Exception => exc STDERR.puts "E: #{exc.message}" #STDERR.puts ARGV.options.to_s raise if DEBUG end exit err_code