#!/usr/bin/env python """ zcfv - Zlib CRC File Validator (1999-11-18) ------------------------------------------ Most of the time you will need no command line arguments. By default if zfv-crc.txt exists in the current directory, files will be verified; if it doesn't exist, then a zfv-crc.txt file will be created. -t - force TEST files in current directory against stored CRC values -c - force CREATION (or re-creation) of CRC text file in current directory -s - case SENSITIVITY in filenames, default is non-sensitive -r - RECURSE through subdirectories (writes/reads file in each dir) -x - EXPUNGE (delete) the zfv-crc.txt file from directory(s) -d - DELETE any files with mismatched CRC values (use with care!) -1 - do not write CRC creation lines to screen (default: screen & file) -2 - do not write CRC creation lines to file (default: screen & file) -f filename - alternate FILENAME to use other than zfv-crc.txt -l logfile - LOG errors to this file (warning: no file sharing support) -b buffersize - set the file BUFFER size in bytes (default: 131072) CRC calculations here are ZIP/Zlib compatible (not SFV compatible). T. Middleton - http://www.vex.net/~x/ """ # note: you can change the buffer size (default 256k) below at bufsize= crcfilename = "zfv-crc.txt" recurse=0 # actually technically it's not recursion mode=0 # 0 = auto; 2 = test; 1 = create; printmode=3 # 3 = stdout + file; 1 = file only; 2 = stdout only caseSensitive=0 # filename matches deathmode = 0 # delete files with wrong CRC buffersize = 131072 # file buffer size, default 131072 bytes (128k) import os,sys,re import zlib,glob,time,getopt,string reFv = re.compile(r"^([^#\n]\S+)\s+(\S+\s+\S+)\s+(\d+)\s\s(.*)\s*$",re.M) Gerrors = 0 # global errors (collected when recursing) Gbytes = 0 Gfiles = 0 logfile = None dirs = [None] def crcfile(fn,bufsize=131072): """Calculate CRC for the given filename, return longint """ crc = None try: f = open(fn,"rb") while 1: s = f.read(bufsize) if crc==None: crc = zlib.crc32(s) else: crc = zlib.crc32(s,crc) if len(s)crcfilename: stat = os.stat(fn) crc = crcfile(fn,buffersize) crcprint(fout,"%08X %s%11d %s" % ( crc, time.strftime("%Y-%m-%y %H:%M:%S",time.localtime(stat[8])), stat[6], fn ) ) fcount = fcount + 1 bcount = bcount + stat[6] return (fcount,bcount) def crcprint(fout,s): """Print to file and/or stdout """ if (printmode & 1 and fout): fout.write(s+"\n") if printmode & 2: print s def errprint(logfile,s): """Append error to log file, and print to screen """ if logfile: try: f = open(logfile,'a') #f.seek(0,2) f.write(s+"\n") f.close() except IOError,msg: pass print s def calcfiles(): """Calculate all the CRCs """ global Gbytes,Gfiles try: fout = open(crcfilename,'w') tnow = time.time() crcprint(fout,"# zcfv CRC data - %s\n" % time.strftime("%Y-%m-%y %H:%M:%S (%A)",time.localtime(time.time()))) fcount,bcount = dodir(fout) crcprint(fout,"\n# %s files (%d bytes) processed in %.2f seconds." % ( fcount, bcount, time.time() - tnow) ) if (printmode & 1 and fout): fout.write("# Get zcfv from http://www.geocities.com/SoHo/1826/zcfv.html\n") Gfiles = Gfiles + fcount Gbytes = Gbytes + bcount fout.close() except IOError,msg: print "ABORT: Error writing to %s: %s" % ( os.path.abspath(crcfilename),msg ) sys.exit() def compfiles(): """Load and parse and compare CRC file to actual files in current dir """ global Gfiles,Gbytes try: f = open(crcfilename,"r") fv = f.read() f.close() except IOError,msg: print "ABORT: Error reading to %s: %s" % (crcfilename,msg) sys.exit() vals = reFv.findall(fv) dict = {} for v in vals: if caseSensitive: dict[v[-1]] = v[:-1] else: dict[string.upper(v[-1])] = v[:-1] errors = 0 warnings = 0 files = glob.glob("*") for fn in files: if os.path.isdir(fn): if recurse: dirs.append(os.path.abspath(fn)) elif fn<>crcfilename: if caseSensitive: gotfile=dict.get(fn,None) else: gotfile=dict.get(string.upper(fn),None) if gotfile<>None: crc = crcfile(fn,buffersize) stat = os.stat(fn) Gbytes = Gbytes + stat[6] if gotfile[0] <> ("%08X" % crc): errprint(logfile,"! Bad CRC: %08X should be %s for %s" % ( crc,gotfile[0],os.path.abspath(fn),) ) errors = errors + 1 if deathmode: try: os.unlink(fn) print "# Deleted %s" % os.path.abspath(fn) except IOError,msg: errprint(logfile,"! Couldn't delete %s" % ( os.path.abspath(fn),) ) else: print "* %s is in directory, but not in %s." % (fn,crcfilename) warnings = warnings + 1 if warnings==0 and errors==0: print "Out of %s files all appear correct." % len(dict.keys()) if warnings>0: print "* %s files are listed in %s, but %s in the directory." % ( len(dict.keys()),crcfilename,len(files) ) if errors>0: print "! Out of %s files %s have incorrect CRC values." % ( len(dict.keys()),errors ) Gfiles = Gfiles + len(dict.keys()) return errors def getdirs(): """return list of subdirectories of current directory """ fns = glob.glob("*") dirs = [] for fn in fns: if os.path.isdir(fn): dirs.append(os.path.abspath(fn)) return dirs def help(): print globals()["__doc__"] sys.exit() opts,args = getopt.getopt(sys.argv[1:],"12CcDdHhRrSsTtXxB:b:F:f:L:l:") for opt,arg in opts: if opt=="-h" or opt=="-H" or opt=="-?": help() elif opt=="-f" or opt=="-F": crcfilename = arg elif opt=="-l" or opt=="-L": logfile = arg elif opt=="-b" or opt=="-B": try: buffersize = string.atoi(arg) except: print "! Invalid buffersize (%s), using default (%s)" % ( arg,buffersize ) elif opt=="-c" or opt=="-C": mode = 1 elif opt=="-t" or opt=="-T": mode = 2 elif opt=="-x" or opt=="-X": mode = 3 elif opt=="-s" or opt=="-S": caseSensitive = 1 elif opt=="-r" or opt=="-R": recurse = 1 elif opt=="-d" or opt=="-D": deathmode = 1 elif opt=="-1": printmode = 1 elif opt=="-2": printmode = 2 for arg in args: if arg=="c" or arg=="C": mode = 1 elif arg=="t" or arg=="T": mode = 2 elif arg=="x" or arg=="X": mode = 3 elif arg=="/?" or arg=="/h" or arg=="/H": help() else: crcfilename = arg dcount = 0 dnow = time.time() odir = os.getcwd() while dirs: if dirs[0]<>None: os.chdir(os.path.abspath(dirs[0])) print "# Entering %s..." % os.path.abspath(dirs[0]) if mode == 0: if os.path.exists(crcfilename): mode = 2 else: mode = 1 if mode == 3: if os.path.exists(crcfilename): try: os.unlink(crcfilename) print "# Delated %s" % os.path.abspath(crcfilename) except IOError,msg: print "! Can't delete %s" % os.path.abspath(crcfilename) if recurse: dirs[len(dirs):len(dirs)] = getdirs() elif mode == 1: calcfiles() else: Gerrors = Gerrors + compfiles() dcount = dcount + 1 del dirs[0] if dcount>1: print "# Scanned %s directories (%s files, %d bytes) in %.2f seconds" % ( dcount,Gfiles,Gbytes,time.time() - dnow ) if Gerrors > 0: print "# There were %s mismatching CRC values found in total!" % Gerrors os.chdir(odir)