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