#!/usr/local/bin/python

import shutil
import anydbm
import sys
import string
import md5
import stat
import os

def usage():
	sys.stderr.write('Usage: %s [-i databasefile initialrepetitions] [-r databasefile initialrepetitions] [-e databasefile]\n' % sys.argv[0])
	sys.stderr.write('"-i databasefile repetitions" says initialize the database.\n')
	sys.stderr.write('    Repetitions is the max number of times we will try to copy a given file\n')
	sys.stderr.write('"-r databasefile" says restart: continue counting down repetitions\n')
	sys.stderr.write('"-e databasefile" says delete a preexisting database\n')
	sys.stderr.write('"-d sourcehier desthier" says to copy data from sourcehier to desthier\n')
	sys.stderr.write('"-m databasefile repetitions filename" says to set filename\'s repetition count to a specific\n')
	sys.stderr.write('    value, manually\n')
	sys.stderr.write('"-g database filename" says get the value for filename\'s repetition count\n')
	sys.stderr.write('"-s database" says to summarize counter status for all files in the database\n')
	sys.stderr.write('"-v n" says to operate verbosely.  Higher n is more verbose.  1 is only for definite error conditions,\n')
	sys.stderr.write('    2 is surprise (non-)preexistence conditions, and 3 is for the whole ball of wax\n')
	sys.stderr.write('"-c shellcommand n" says to run shellcommand after attempting to copy n files.  If the command\n')
	sys.stderr.write('    returns POSIX shell false, %s will exit.  Otherwise we continue\n' % sys.argv[0])
	sys.stderr.write('"-C n" says that if %s sees n consecutive file errors, terminate prematurely\n')
	sys.stderr.write('\n')
	sys.stderr.write('-i, -r, -m and -e are mutually exclusive\n')
	sys.stderr.write('\n')
	sys.stderr.write('Only regular files, directories and symlinks are handled at this time.  Hard links are not\n')
	sys.stderr.write('preserved, their relationship will be broken silently\n')
	sys.stderr.write('\n')
	sys.stderr.write('This program uses the python anydbm interface, so it may seemingly at random choose a backend\n')
	sys.stderr.write('database like berkeley db, gdbm, dbm, dumbdbm or others.  However, once a database of a given\n')
	sys.stderr.write('name is created, subsequent usage of that same database name should come up with the same type.\n')
	sys.exit(1)

def myjoin(dir,file):
	filename = os.path.join(dir,file)
	while filename[0:2] == '//' or filename[0:2] == './':
		# IMO, os.path should handle this for us...
		if filename[0:2] == '//':
			# strip off one redundant /
			filename = filename[1:]
		if filename[0:2] == './':
			if filename[2:]:
				# strip off the leading "./"; it'll just slow us down
				filename = filename[2:]
			else:
				# change "./" to just "."
				filename = '.'
	return filename

def mycopy(srcfile,dstfile,blocksize=1024*1024):
	# in this function, we catch and die on output errors, while we allow
	# the caller to catch exceptions on input errors.  This is because
	# this program is for -reading- from an unreliable filesystem, not
	# -writing- to an unreliable filesystem
	infile = open(srcfile,'r')
	try:
		outfile = open(dstfile,'w')
	except exceptions, (errno, detail):
		sys.stderr.write('%s: error opening %s for writing.  Exiting prematurely: %d, %s\n' % (sys.argv[0], dstfile, errno, detail))
		sys.exit(1)
	while 1:
		block = infile.read(blocksize)
		if not block:
			break
		if verbose >= 4:
			sys.stderr.write('Read block of length %d\n' % len(block))
		try:
			outfile.write(block)
		except exceptions, (errno, detail):
			sys.stderr.write('%s: error writing to %s.  Exiting prematurely: %d %s\n' % (sys.argv[0], dstfile, errno, detail))
			sys.exit(1)
		if verbose >= 4:
			sys.stderr.write('Wrote block of length %d\n' % len(block))
	infile.close()
	try:
		outfile.close()
	except exceptions, (errno, detail):
		sys.stderr.write('%s: error closing %s.  Exiting prematurely: %d %s\n' % (sys.argv[0], dstfile, errno, detail))
		sys.exit(1)
	# someday we might want to do these via os.* functions, so we can
	# catch output errors and ignore (IE, let the caller catch) input errors
	shutil.copymode(srcfile,dstfile)
	shutil.copystat(srcfile,dstfile)

def should_we_continue(db):
	global files_done
	global too_many_consecutive_errors
	global consecutive_errors_seen
	global files_done
	global continuetest_every
	sys.stderr.write('files_done is %d\n' % files_done)
	if continuetest_every != 0 and files_done % continuetest_every == 0:
		#files_done = 1L
		exit_status = os.system(continuetest)
		if exit_status == 0:
			sys.stderr.write('Continuation test "%s" returned true, continuing processing\n' % continuetest)
		else:
			sys.stderr.write('Sorry, continuation test "%s" returned false, terminating prematurely\n\n' % continuetest)
			perform_summarization(db)
			sys.exit(1)
	files_done += 1L
	if too_many_consecutive_errors != 0 and consecutive_errors_seen == too_many_consecutive_errors:
		sys.stderr.write('%s: Sorry, too many consecutive errors seen, terminating prematurely\n\n' % sys.argv[0])
		perform_summarization(db)
		sys.exit(1)

def traverse(db,repetitions,file='.',priorpath='.',initial=1):
	global consecutive_errors_seen
	# add the "file" to the database, whether it stats OK or not -
	# otherwise we wouldn't retry it later :)
	filename = myjoin(priorpath,file)
	should_we_continue(db)
	if initial:
		if verbose >= 2:
			sys.stderr.write('Putting file %s in the database with %d repetitions\n' % (filename, repetitions))
		db[filename] = str(repetitions)
	else:
		if db.has_key(filename):
			if verbose >= 3:
				sys.stderr.write('Good, redetected %s\n' % filename)
		else:
			if verbose >= 2:
				sys.stderr.write('Indication of flakiness?  Found %s this time, but not on previous runs\n' % filename)
			db[filename] = str(repetitions)
	# lstat_ok logic prevents trying crashes due to stat'ing a file
	# that was there "just a minute ago"
	try:
		s = os.lstat(file)
	except exceptions, (errno, detail):
		lstat_ok = 0
		sys.stderr.write('lstat failed: %d %s\n' % (errno, detail))
	else:
		lstat_ok = 1
	if lstat_ok:
		consecutive_errors_seen = 0
		if stat.S_ISDIR(s[stat.ST_MODE]):
			# recurse
			os.chdir(file)
			files = os.listdir('.')
			files.sort()
			for filename in files:
				if filename <> '.' and filename <> '..':
					traverse(db,repetitions,filename,myjoin(priorpath,file),initial)
			if file <> '.':
				# only backup a level, if we actually descended a level previously!
				# otherwise, we chmod something a level too high in the tree...
				os.chdir('..')
	else:
		consecutive_errors_seen += 1

def decrement(key):
	# seems to be pass by value...
	#sys.stderr.write('Decrement starting with %s\n' % key)
	newkey = str(string.atoi(key)-1)
	if newkey == '0':
		# failed too many times
		newkey = failed
	#sys.stderr.write('Decrement ending with %s\n' % newkey)
	return newkey
		
def handle_dir(db,k,srcfile,srcsb,dstfile,dstsb):
	global consecutive_errors_seen
	do_decrement = 0
	if dstsb != None:
		# destination directory preexists
		if stat.S_ISDIR(dstsb[stat.ST_MODE]):
			if srcsb[stat.ST_MODE] != dstsb[stat.ST_MODE]:
				if verbose >= 2:
					sys.stderr.write('Directory %s preexists, but modes do not match: %o %o\n' % (dstfile, srcsb[stat.ST_MODE], dstsb[stat.ST_MODE]))
				try:
					os.chmod(dstfile,srcsb[stat.ST_MODE] & 07777)
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Modifying the modes of %s failed, decrementing: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
			if srcsb[stat.ST_UID] != dstsb[stat.ST_UID] or srcsb[stat.ST_GID] != dstsb[stat.ST_GID]:
				if verbose >= 2:
					sys.stderr.write('Directory %s preexists, but ownerships do not match: uid %d gid %d, uid %d gid %d.  Attempting to chown\n' % \
						(dstfile, srcsb[stat.ST_UID], srcsb[stat.ST_GID], dstsb[stat.ST_UID], dstsb[stat.ST_GID]))
				try:
					os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
				except exceptions, (errno, detail):
					do_decrement = 1
					sys.stderr.write('chown failed: %d %s\n' % (errno, detail))
		else:
			if verbose >= 2:
				sys.stderr.write('%s preexists, but is not a directory.  Attempting to unlink and recreate\n' % dstfile)
			try:
				os.unlink(dstfile)
				os.mkdir(dstfile,srcsb[stat.ST_MODE] & 07777)
				os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
			except exceptions, (errno, detail):
				sys.stderr.write('unlink, mkdir or chown failed: %d %s\n' % (errno, detail))
				do_decrement = 1
				if verbose >= 1:
					sys.stderr.write('Removal and recreation of directory %s failed\n' % dstfile)
	else:
		# destination directory does not preexist
		if verbose >= 3:
			sys.stderr.write('Creating fresh directory %s\n' % srcfile)
		try:
			os.mkdir(dstfile,srcsb[stat.ST_MODE] & 07777)
			os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
			db[k] = succeeded
		except exceptions, (errno, detail):
			if verbose >= 1:
				sys.stderr.write('Creation of fresh directory %s failed: %d %s\n' % (dstfile, errno, detail))
			do_decrement = 1
	if do_decrement:
		sys.stderr.write('Transfer failed, attempts remaining: %s\n\n' % db[k])
		consecutive_errors_seen += 1
		db[k] = decrement(db[k])
	else:
		sys.stderr.write('Transfer succeeded\n\n')
		consecutive_errors_seen = 0
		db[k] = succeeded


def handle_regular(db,k,srcfile,srcsb,dstfile,dstsb):
	global consecutive_errors_seen
	do_decrement = 0
	if dstsb != None:
		# destination file preexists
		if stat.S_ISREG(dstsb[stat.ST_MODE]):
			if srcsb[stat.ST_MODE] != dstsb[stat.ST_MODE]:
				if verbose >= 2:
					sys.stderr.write('Regular file %s preexists, but modes do not match: %o %o\n' % (dstfile, srcsb[stat.ST_MODE], dstsb[stat.ST_MODE]))
				try:
					os.chmod(dstfile,srcsb[stat.ST_MODE] & 07777)
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Modifying the modes of regular file %s failed, decrementing: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
			if srcsb[stat.ST_UID] != dstsb[stat.ST_UID] or srcsb[stat.ST_GID] != dstsb[stat.ST_GID]:
				if verbose >= 2:
					sys.stderr.write('Regular file %s preexists, but ownerships do not match: uid %d gid %d, uid %d gid %d.  Attempting to chown\n' % \
						(dstfile, srcsb[stat.ST_UID], srcsb[stat.ST_GID], dstsb[stat.ST_UID], dstsb[stat.ST_GID]))
				try:
					os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Modifying the ownerships of regular file %s failed, decrementing: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
			if srcsb[stat.ST_SIZE] != dstsb[stat.ST_SIZE]:
				if verbose >= 2:
					sys.stderr.write('Regular file %s preexists, but file lengths do not match: %d, %d.  Attempting to remove and recopy\n' % \
						(dstfile, srcsb[stat.ST_SIZE], dstsb[stat.ST_SIZE]))
				try:
					os.unlink(dstfile)
					mycopy(srcfile,dstfile)
					#shutil.copy2(srcfile,dstfile)
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Removal and recreation of regular file %s failed: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
		else:
			if verbose >= 2:
				sys.stderr.write('%s preexists, but is not a regular file.  Attempting to unlink/rmdir and recreate\n' % dstfile)
			try:
				if stat.S_ISDIR(dstsb[stat.ST_MODE]):
					os.rmdir(dstfile)
				else:
					os.unlink(dstfile)
				mycopy(srcfile,dstfile)
				#shutil.copy2(srcfile,dstfile)
			except exceptions, (errno, detail):
				if verbose >= 1:
					sys.stderr.write('Removal and recreation of %s failed: %d %s\n' % (dstfile, errno, detail))
				do_decrement = 1
	else:
		# destination file does not preexist
		if verbose >= 3:
			sys.stderr.write('Creating fresh regular file %s\n' % srcfile)
		try:
			mycopy(srcfile,dstfile)
			#shutil.copy2(srcfile,dstfile)
		except exceptions, (errno, detail):
			if verbose >= 1:
				sys.stderr.write('Creation of fresh regular file %s failed: %d %s\n' % (dstfile, errno, detail))
			do_decrement = 1
	if do_decrement:
		sys.stderr.write('Transfer failed, attempts remaining: %s\n\n' % db[k])
		consecutive_errors_seen += 1
		db[k] = decrement(db[k])
	else:
		sys.stderr.write('Transfer succeeded\n\n')
		consecutive_errors_seen = 0
		db[k] = succeeded

def handle_symlink(db,k,srcfile,srcsb,dstfile,dstsb):
	global consecutive_errors_seen
	do_decrement = 0
	if dstsb != None:
		# destination file preexists
		if stat.S_ISLNK(dstsb[stat.ST_MODE]):
			# modes and owners are ignored on symlinks
			srctarg = os.readlink(srcfile)
			dsttarg = os.readlink(dstfile)
			if srctarg != dsttarg:
				if verbose >= 2:
					sys.stderr.write('Symlink %s preexists, but does not point at the same place: %s %s.  will attempt to unlink and recreate\n' % (srctarg, dsttarg))
				try:
					os.unlink(dstfile)
					os.symlink(srctarg, dstfile)
				except exceptions, (errno, detail):
					do_decrement = 1
					if verbose >= 1:
						sys.stderr.write('Removal and recreation of symlink %s failed: %d %s\n' % (dstfile, errno, detail))
		else:
			if verbose >= 2:
				sys.stderr.write('%s preexists, but is not a symlink.  Attempting to unlink/rmdir and recreate\n' % dstfile)
			try:
				if stat.S_ISDIR(dstsb[stat.ST_MODE]):
					os.rmdir(dstfile)
				else:
					os.unlink(dstfile)
				srctarg = os.readlink(srcfile)
				os.symlink(srctarg, dstfile)
			except exceptions, (errno, detail):
				do_decrement = 1
				if verbose >= 1:
					sys.stderr.write('Removal and recreation of symlink %s failed: %d %s\n' % (dstfile, errno, detail))
	else:
		# destination file does not preexist
		if verbose >= 3:
			sys.stderr.write('Creating fresh symlink %s\n' % srcfile)
		try:
			srctarg = os.readlink(srcfile)
			os.symlink(srctarg, dstfile)
		except exceptions, (errno, detail):
			if verbose >= 1:
				sys.stderr.write('Creation of fresh symlink failed: %d %s\n' % (dstfile, errno, detail))
			do_decrement = 1
	if do_decrement:
		sys.stderr.write('Transfer failed, attempts remaining: %s\n\n' % db[k])
		consecutive_errors_seen += 1
		db[k] = decrement(db[k])
	else:
		sys.stderr.write('Transfer succeeded\n\n')
		consecutive_errors_seen = 0
		db[k] = succeeded

def perform_summarization(db):
	sys.stderr.write('Generating summary results\n')
	good=bad=0
	max = -1
	counts = {}
	for k in db.keys():
		# a good one will be zero.  A bad one will be >= 1 or -1
		if counts.has_key(db[k]):
			counts[db[k]] += 1
		else:
			counts[db[k]] = 1

	ks = counts.keys()
	ks.sort()
	print
	for k in ks:
		if k == succeeded:
			print 'Succeeded:',counts[k]
			good = counts[k]
		elif k == failed:
			print 'Failed:',counts[k]
			bad = counts[k]
		else:
			print k+':',counts[k]
	if len(db) == good + bad:
		sys.stderr.write('\nYou have reached a steady state.  Rerunning this program should not change things any further\n')

def count_down(db,srcdir,dstdir):
	sys.stderr.write("Starting count down\n")
	keys = db.keys()
	keys.sort()
	os.chdir(dstdir)
	keyno = 0
	numkeys = len(keys)
	for k in keys:
		if verbose >= 3:
			sys.stderr.write('Looking at keyno %d of %d\n' % (keyno, numkeys))
		keyno += 1
		if string.atoi(db[k]) > 0:
			should_we_continue(db)
#			if verbose >= 3:
#				sys.stderr.write('File %s has not succeeded or failed yet\n' % k)
			srcfile=myjoin(srcdir,k)
			dstfile=myjoin(dstdir,k)
			try:
				srcsb=os.lstat(srcfile)
			except exceptions, (errno, detail):
				sys.stderr.write('Woah...  Source file %s does not exist?  Decrementing!: %d %s\n' % (srcfile, errno, detail))
				db[k] = decrement(db[k])
				srcsb=None
				continue
			else:
				if verbose >= 2:
					sys.stderr.write('lstat of source file %s succeeded\n' % srcfile)
			try:
				dstsb=os.lstat(dstfile)
			except exceptions, (errno, detail):
				dstsb=None
				if verbose >= 2:
					sys.stderr.write("Destination file %s not stat'able, but that's at least semi-normal: %d %s\n" % (dstfile, errno, detail))
			else:
				if verbose >= 2:
					sys.stderr.write('lstat of destination file %s succeeded\n' % dstfile)
			if stat.S_ISDIR(srcsb[stat.ST_MODE]):
				handle_dir(db,k,srcfile,srcsb,dstfile,dstsb)
			elif stat.S_ISREG(srcsb[stat.ST_MODE]):
				handle_regular(db,k,srcfile,srcsb,dstfile,dstsb)
			elif stat.S_ISLNK(srcsb[stat.ST_MODE]):
				handle_symlink(db,k,srcfile,srcsb,dstfile,dstsb)
			else:
				sys.stderr.write('Erroring out on source file %s: It is not a directory, link or regular file\n' % srcfile)
				sys.stderr.write('Transfer terminated prematurely!\n')
				sys.exit(1)
	perform_summarization(db)

dstdir=srcdir=''
verbose=init=erase=restart=manual=get=summarize=0

succeeded = "-10"
failed = "-20"

files_done = 0L

too_many_consecutive_errors = 0
consecutive_errors_seen = 0
continuetest_every = 0

exceptions = (IOError, OSError)
#except IOError, (errno, detail)
#except exceptions, (errno, detail):

while sys.argv[1:]:
	if sys.argv[3:] and sys.argv[1] == '-i':
		init = 1
		database=sys.argv[2]
		repetitions=string.atoi(sys.argv[3])
		if repetitions < 1:
			sys.stderr.write('Repetitions must be >= 1\n')
			usage()
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[2:] and sys.argv[1] == '-e':
		erase = 1
		database=sys.argv[2]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[2:] and sys.argv[1] == '-s':
		summarize = 1
		database=sys.argv[2]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-d':
		srcdir = sys.argv[2]
		dstdir = sys.argv[3]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-r':
		restart = 1
		database=sys.argv[2]
		repetitions=string.atoi(sys.argv[3])
		if repetitions < 1:
			sys.stderr.write('Repetitions must be >= 1\n')
			usage()
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-c':
		continuetest=sys.argv[2]
		continuetest_every=string.atoi(sys.argv[3])
		if continuetest_every < 1:
			sys.stderr.write('second argument to -c skip must be >= 1\n')
			usage()
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-m':
		manual = 1
		database = sys.argv[2]
		repetitions=string.atoi(sys.argv[3])
		filename = sys.argv[4]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-g':
		get = 1
		database = sys.argv[2]
		filename = sys.argv[3]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[1:] and sys.argv[1] == '-v':
		verbose = string.atoi(sys.argv[2])
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[1:] and sys.argv[1] == '-C':
		too_many_consecutive_errors = string.atoi(sys.argv[2])
		del sys.argv[1]
		del sys.argv[1]
	else:
		usage()

# these should all be 0 or 1, and they are mutually exclusive, so their
# sum must be exactly 1, no less, no more
if init + erase + restart + manual + get + summarize != 1:
	sys.stderr.write('%s: -i, -r, -m, -g, -s and -e are mutually exclusive, and exactly one must be specified\n' % sys.argv[0])
	usage()

if (init or restart) and (srcdir == '' or dstdir == ''):
	sys.stderr.write('-i and -r both require -d\n')
	usage()
	
if manual:
	sys.stderr.write('Manually setting %s to %d in %s\n' % (filename, repetitions, database))
	try:
		db = anydbm.open(database,'r')
		db.close()
	except exceptions, (errno, detail):
		sys.stderr.wrtite('Sorry, database %s does not exist.  Please use -i: %d %s\n' % (database, errno, detail))
		sys.exit(1)
	db = anydbm.open(database,'w')
	db[filename] = str(repetitions)
	db.close()
	sys.exit(0)

if manual:
	sys.stderr.write('Manually setting %s to %d in %s\n' % (filename, repetitions, database))
	try:
		db = anydbm.open(database,'r')
		db.close()
	except exceptions, (errno, detail):
		sys.stderr.wrtite('Sorry, database %s does not exist.  Please use -i: %d %s\n' % (database, errno, detail))
		sys.exit(1)
	db = anydbm.open(database,'w')
	db[filename] = str(repetitions)
	db.close()
	sys.exit(0)

if get:
	sys.stderr.write('Retrieving count for file %s in %s....\n' % (filename, database))
	db = anydbm.open(database,'r')
	if db.has_key(filename):
		value = db[filename]
	else:
		sys.stderr.write('Sorry, database %s does not have file %s in it\n' % (database, filename))
		sys.exit(1)
	if value == succeeded:
		sys.stdout.write('Value is: succeeded (%s is the corresponding sentinel value)\n' % value)
	elif value == failed:
		sys.stdout.write('Value is: failed (%s is the corresponding sentinel value)\n' % value)
	else:
		sys.stdout.write('Value is: %s repetitions remaining\n' % value)
	#sys.stdout.write('%s\n' % db[filename])
	db.close()
	sys.exit(0)

if init:
	# if the database preexists, error out
	try:
		db = anydbm.open(database,'r')
		db.close()
	except (anydbm.error):
		if verbose >= 2:
			sys.stderr.write('Database nonexistent, but that is normal\n')
		pass
	else:
		sys.stderr.write('When using -i, the database must not preexist.  Please use -e\n')
		usage()
	sys.stderr.write('Initializing....\n')
	db = anydbm.open(database,'c')
	os.chdir(srcdir)
	traverse(db,repetitions,initial=1)
	count_down(db,srcdir,dstdir)
	db.close()
	sys.exit(0)

if restart:
	# just check to be sure it already exists
	try:
		db = anydbm.open(database,'r')
		db.close()
	except (anydbm.error):
		sys.stderr.write('Sorry, database %s does not exist.  Please use -i\n')
		sys.exit(1)
	db = anydbm.open(database,'w')
	os.chdir(srcdir)
	traverse(db,repetitions,initial=0)
	count_down(db,srcdir,dstdir)
	db.close()
	sys.exit(0)

if summarize:
	# just check to be sure it already exists
	try:
		db = anydbm.open(database,'r')
	except (anydbm.error):
		sys.stderr.write('Sorry, database %s does not exist.  Please use -i\n')
		sys.exit(1)
	perform_summarization(db)
	db.close()
	sys.exit(0)


#!/usr/local/bin/python

import shutil
import anydbm
import sys
import string
import md5
import stat
import os

def usage():
	sys.stderr.write('Usage: %s [-i databasefile initialrepetitions] [-r databasefile initialrepetitions] [-e databasefile]\n' % sys.argv[0])
	sys.stderr.write('"-i databasefile repetitions" says initialize the database.\n')
	sys.stderr.write('    Repetitions is the max number of times we will try to copy a given file\n')
	sys.stderr.write('"-r databasefile" says restart: continue counting down repetitions\n')
	sys.stderr.write('"-e databasefile" says delete a preexisting database\n')
	sys.stderr.write('"-d sourcehier desthier" says to copy data from sourcehier to desthier\n')
	sys.stderr.write('"-m databasefile repetitions filename" says to set filename\'s repetition count to a specific\n')
	sys.stderr.write('    value, manually\n')
	sys.stderr.write('"-g database filename" says get the value for filename\'s repetition count\n')
	sys.stderr.write('"-s database" says to summarize counter status for all files in the database\n')
	sys.stderr.write('"-v n" says to operate verbosely.  Higher n is more verbose.  1 is only for definite error conditions,\n')
	sys.stderr.write('    2 is surprise (non-)preexistence conditions, and 3 is for the whole ball of wax\n')
	sys.stderr.write('"-c shellcommand n" says to run shellcommand after attempting to copy n files.  If the command\n')
	sys.stderr.write('    returns POSIX shell false, %s will exit.  Otherwise we continue\n' % sys.argv[0])
	sys.stderr.write('"-C n" says that if %s sees n consecutive file errors, terminate prematurely\n')
	sys.stderr.write('\n')
	sys.stderr.write('-i, -r, -m and -e are mutually exclusive\n')
	sys.stderr.write('\n')
	sys.stderr.write('Only regular files, directories and symlinks are handled at this time.  Hard links are not\n')
	sys.stderr.write('preserved, their relationship will be broken silently\n')
	sys.stderr.write('\n')
	sys.stderr.write('This program uses the python anydbm interface, so it may seemingly at random choose a backend\n')
	sys.stderr.write('database like berkeley db, gdbm, dbm, dumbdbm or others.  However, once a database of a given\n')
	sys.stderr.write('name is created, subsequent usage of that same database name should come up with the same type.\n')
	sys.exit(1)

def myjoin(dir,file):
	filename = os.path.join(dir,file)
	while filename[0:2] == '//' or filename[0:2] == './':
		# IMO, os.path should handle this for us...
		if filename[0:2] == '//':
			# strip off one redundant /
			filename = filename[1:]
		if filename[0:2] == './':
			if filename[2:]:
				# strip off the leading "./"; it'll just slow us down
				filename = filename[2:]
			else:
				# change "./" to just "."
				filename = '.'
	return filename

def mycopy(srcfile,dstfile,blocksize=1024*1024):
	# in this function, we catch and die on output errors, while we allow
	# the caller to catch exceptions on input errors.  This is because
	# this program is for -reading- from an unreliable filesystem, not
	# -writing- to an unreliable filesystem
	infile = open(srcfile,'r')
	try:
		outfile = open(dstfile,'w')
	except exceptions, (errno, detail):
		sys.stderr.write('%s: error opening %s for writing.  Exiting prematurely: %d, %s\n' % (sys.argv[0], dstfile, errno, detail))
		sys.exit(1)
	while 1:
		block = infile.read(blocksize)
		if not block:
			break
		if verbose >= 4:
			sys.stderr.write('Read block of length %d\n' % len(block))
		try:
			outfile.write(block)
		except exceptions, (errno, detail):
			sys.stderr.write('%s: error writing to %s.  Exiting prematurely: %d %s\n' % (sys.argv[0], dstfile, errno, detail))
			sys.exit(1)
		if verbose >= 4:
			sys.stderr.write('Wrote block of length %d\n' % len(block))
	infile.close()
	try:
		outfile.close()
	except exceptions, (errno, detail):
		sys.stderr.write('%s: error closing %s.  Exiting prematurely: %d %s\n' % (sys.argv[0], dstfile, errno, detail))
		sys.exit(1)
	# someday we might want to do these via os.* functions, so we can
	# catch output errors and ignore (IE, let the caller catch) input errors
	shutil.copymode(srcfile,dstfile)
	shutil.copystat(srcfile,dstfile)

def should_we_continue(db):
	global files_done
	global too_many_consecutive_errors
	global consecutive_errors_seen
	global files_done
	global continuetest_every
	sys.stderr.write('files_done is %d\n' % files_done)
	if continuetest_every != 0 and files_done % continuetest_every == 0:
		#files_done = 1L
		exit_status = os.system(continuetest)
		if exit_status == 0:
			sys.stderr.write('Continuation test "%s" returned true, continuing processing\n' % continuetest)
		else:
			sys.stderr.write('Sorry, continuation test "%s" returned false, terminating prematurely\n\n' % continuetest)
			perform_summarization(db)
			sys.exit(1)
	files_done += 1L
	if too_many_consecutive_errors != 0 and consecutive_errors_seen == too_many_consecutive_errors:
		sys.stderr.write('%s: Sorry, too many consecutive errors seen, terminating prematurely\n\n' % sys.argv[0])
		perform_summarization(db)
		sys.exit(1)

def traverse(db,repetitions,file='.',priorpath='.',initial=1):
	global consecutive_errors_seen
	# add the "file" to the database, whether it stats OK or not -
	# otherwise we wouldn't retry it later :)
	filename = myjoin(priorpath,file)
	should_we_continue(db)
	if initial:
		if verbose >= 2:
			sys.stderr.write('Putting file %s in the database with %d repetitions\n' % (filename, repetitions))
		db[filename] = str(repetitions)
	else:
		if db.has_key(filename):
			if verbose >= 3:
				sys.stderr.write('Good, redetected %s\n' % filename)
		else:
			if verbose >= 2:
				sys.stderr.write('Indication of flakiness?  Found %s this time, but not on previous runs\n' % filename)
			db[filename] = str(repetitions)
	# lstat_ok logic prevents trying crashes due to stat'ing a file
	# that was there "just a minute ago"
	try:
		s = os.lstat(file)
	except exceptions, (errno, detail):
		lstat_ok = 0
		sys.stderr.write('lstat failed: %d %s\n' % (errno, detail))
	else:
		lstat_ok = 1
	if lstat_ok:
		consecutive_errors_seen = 0
		if stat.S_ISDIR(s[stat.ST_MODE]):
			# recurse
			os.chdir(file)
			files = os.listdir('.')
			files.sort()
			for filename in files:
				if filename <> '.' and filename <> '..':
					traverse(db,repetitions,filename,myjoin(priorpath,file),initial)
			if file <> '.':
				# only backup a level, if we actually descended a level previously!
				# otherwise, we chmod something a level too high in the tree...
				os.chdir('..')
	else:
		consecutive_errors_seen += 1

def decrement(key):
	# seems to be pass by value...
	#sys.stderr.write('Decrement starting with %s\n' % key)
	newkey = str(string.atoi(key)-1)
	if newkey == '0':
		# failed too many times
		newkey = failed
	#sys.stderr.write('Decrement ending with %s\n' % newkey)
	return newkey
		
def handle_dir(db,k,srcfile,srcsb,dstfile,dstsb):
	global consecutive_errors_seen
	do_decrement = 0
	if dstsb != None:
		# destination directory preexists
		if stat.S_ISDIR(dstsb[stat.ST_MODE]):
			if srcsb[stat.ST_MODE] != dstsb[stat.ST_MODE]:
				if verbose >= 2:
					sys.stderr.write('Directory %s preexists, but modes do not match: %o %o\n' % (dstfile, srcsb[stat.ST_MODE], dstsb[stat.ST_MODE]))
				try:
					os.chmod(dstfile,srcsb[stat.ST_MODE] & 07777)
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Modifying the modes of %s failed, decrementing: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
			if srcsb[stat.ST_UID] != dstsb[stat.ST_UID] or srcsb[stat.ST_GID] != dstsb[stat.ST_GID]:
				if verbose >= 2:
					sys.stderr.write('Directory %s preexists, but ownerships do not match: uid %d gid %d, uid %d gid %d.  Attempting to chown\n' % \
						(dstfile, srcsb[stat.ST_UID], srcsb[stat.ST_GID], dstsb[stat.ST_UID], dstsb[stat.ST_GID]))
				try:
					os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
				except exceptions, (errno, detail):
					do_decrement = 1
					sys.stderr.write('chown failed: %d %s\n' % (errno, detail))
		else:
			if verbose >= 2:
				sys.stderr.write('%s preexists, but is not a directory.  Attempting to unlink and recreate\n' % dstfile)
			try:
				os.unlink(dstfile)
				os.mkdir(dstfile,srcsb[stat.ST_MODE] & 07777)
				os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
			except exceptions, (errno, detail):
				sys.stderr.write('unlink, mkdir or chown failed: %d %s\n' % (errno, detail))
				do_decrement = 1
				if verbose >= 1:
					sys.stderr.write('Removal and recreation of directory %s failed\n' % dstfile)
	else:
		# destination directory does not preexist
		if verbose >= 3:
			sys.stderr.write('Creating fresh directory %s\n' % srcfile)
		try:
			os.mkdir(dstfile,srcsb[stat.ST_MODE] & 07777)
			os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
			db[k] = succeeded
		except exceptions, (errno, detail):
			if verbose >= 1:
				sys.stderr.write('Creation of fresh directory %s failed: %d %s\n' % (dstfile, errno, detail))
			do_decrement = 1
	if do_decrement:
		sys.stderr.write('Transfer failed, attempts remaining: %s\n\n' % db[k])
		consecutive_errors_seen += 1
		db[k] = decrement(db[k])
	else:
		sys.stderr.write('Transfer succeeded\n\n')
		consecutive_errors_seen = 0
		db[k] = succeeded


def handle_regular(db,k,srcfile,srcsb,dstfile,dstsb):
	global consecutive_errors_seen
	do_decrement = 0
	if dstsb != None:
		# destination file preexists
		if stat.S_ISREG(dstsb[stat.ST_MODE]):
			if srcsb[stat.ST_MODE] != dstsb[stat.ST_MODE]:
				if verbose >= 2:
					sys.stderr.write('Regular file %s preexists, but modes do not match: %o %o\n' % (dstfile, srcsb[stat.ST_MODE], dstsb[stat.ST_MODE]))
				try:
					os.chmod(dstfile,srcsb[stat.ST_MODE] & 07777)
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Modifying the modes of regular file %s failed, decrementing: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
			if srcsb[stat.ST_UID] != dstsb[stat.ST_UID] or srcsb[stat.ST_GID] != dstsb[stat.ST_GID]:
				if verbose >= 2:
					sys.stderr.write('Regular file %s preexists, but ownerships do not match: uid %d gid %d, uid %d gid %d.  Attempting to chown\n' % \
						(dstfile, srcsb[stat.ST_UID], srcsb[stat.ST_GID], dstsb[stat.ST_UID], dstsb[stat.ST_GID]))
				try:
					os.chown(dstfile,srcsb[stat.ST_UID],srcsb[stat.ST_GID])
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Modifying the ownerships of regular file %s failed, decrementing: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
			if srcsb[stat.ST_SIZE] != dstsb[stat.ST_SIZE]:
				if verbose >= 2:
					sys.stderr.write('Regular file %s preexists, but file lengths do not match: %d, %d.  Attempting to remove and recopy\n' % \
						(dstfile, srcsb[stat.ST_SIZE], dstsb[stat.ST_SIZE]))
				try:
					os.unlink(dstfile)
					mycopy(srcfile,dstfile)
					#shutil.copy2(srcfile,dstfile)
				except exceptions, (errno, detail):
					if verbose >= 1:
						sys.stderr.write('Removal and recreation of regular file %s failed: %d %s\n' % (dstfile, errno, detail))
					do_decrement = 1
		else:
			if verbose >= 2:
				sys.stderr.write('%s preexists, but is not a regular file.  Attempting to unlink/rmdir and recreate\n' % dstfile)
			try:
				if stat.S_ISDIR(dstsb[stat.ST_MODE]):
					os.rmdir(dstfile)
				else:
					os.unlink(dstfile)
				mycopy(srcfile,dstfile)
				#shutil.copy2(srcfile,dstfile)
			except exceptions, (errno, detail):
				if verbose >= 1:
					sys.stderr.write('Removal and recreation of %s failed: %d %s\n' % (dstfile, errno, detail))
				do_decrement = 1
	else:
		# destination file does not preexist
		if verbose >= 3:
			sys.stderr.write('Creating fresh regular file %s\n' % srcfile)
		try:
			mycopy(srcfile,dstfile)
			#shutil.copy2(srcfile,dstfile)
		except exceptions, (errno, detail):
			if verbose >= 1:
				sys.stderr.write('Creation of fresh regular file %s failed: %d %s\n' % (dstfile, errno, detail))
			do_decrement = 1
	if do_decrement:
		sys.stderr.write('Transfer failed, attempts remaining: %s\n\n' % db[k])
		consecutive_errors_seen += 1
		db[k] = decrement(db[k])
	else:
		sys.stderr.write('Transfer succeeded\n\n')
		consecutive_errors_seen = 0
		db[k] = succeeded

def handle_symlink(db,k,srcfile,srcsb,dstfile,dstsb):
	global consecutive_errors_seen
	do_decrement = 0
	if dstsb != None:
		# destination file preexists
		if stat.S_ISLNK(dstsb[stat.ST_MODE]):
			# modes and owners are ignored on symlinks
			srctarg = os.readlink(srcfile)
			dsttarg = os.readlink(dstfile)
			if srctarg != dsttarg:
				if verbose >= 2:
					sys.stderr.write('Symlink %s preexists, but does not point at the same place: %s %s.  will attempt to unlink and recreate\n' % (srctarg, dsttarg))
				try:
					os.unlink(dstfile)
					os.symlink(srctarg, dstfile)
				except exceptions, (errno, detail):
					do_decrement = 1
					if verbose >= 1:
						sys.stderr.write('Removal and recreation of symlink %s failed: %d %s\n' % (dstfile, errno, detail))
		else:
			if verbose >= 2:
				sys.stderr.write('%s preexists, but is not a symlink.  Attempting to unlink/rmdir and recreate\n' % dstfile)
			try:
				if stat.S_ISDIR(dstsb[stat.ST_MODE]):
					os.rmdir(dstfile)
				else:
					os.unlink(dstfile)
				srctarg = os.readlink(srcfile)
				os.symlink(srctarg, dstfile)
			except exceptions, (errno, detail):
				do_decrement = 1
				if verbose >= 1:
					sys.stderr.write('Removal and recreation of symlink %s failed: %d %s\n' % (dstfile, errno, detail))
	else:
		# destination file does not preexist
		if verbose >= 3:
			sys.stderr.write('Creating fresh symlink %s\n' % srcfile)
		try:
			srctarg = os.readlink(srcfile)
			os.symlink(srctarg, dstfile)
		except exceptions, (errno, detail):
			if verbose >= 1:
				sys.stderr.write('Creation of fresh symlink failed: %d %s\n' % (dstfile, errno, detail))
			do_decrement = 1
	if do_decrement:
		sys.stderr.write('Transfer failed, attempts remaining: %s\n\n' % db[k])
		consecutive_errors_seen += 1
		db[k] = decrement(db[k])
	else:
		sys.stderr.write('Transfer succeeded\n\n')
		consecutive_errors_seen = 0
		db[k] = succeeded

def perform_summarization(db):
	sys.stderr.write('Generating summary results\n')
	good=bad=0
	max = -1
	counts = {}
	for k in db.keys():
		# a good one will be zero.  A bad one will be >= 1 or -1
		if counts.has_key(db[k]):
			counts[db[k]] += 1
		else:
			counts[db[k]] = 1

	ks = counts.keys()
	ks.sort()
	print
	for k in ks:
		if k == succeeded:
			print 'Succeeded:',counts[k]
			good = counts[k]
		elif k == failed:
			print 'Failed:',counts[k]
			bad = counts[k]
		else:
			print k+':',counts[k]
	if len(db) == good + bad:
		sys.stderr.write('\nYou have reached a steady state.  Rerunning this program should not change things any further\n')

def count_down(db,srcdir,dstdir):
	sys.stderr.write("Starting count down\n")
	keys = db.keys()
	keys.sort()
	os.chdir(dstdir)
	keyno = 0
	numkeys = len(keys)
	for k in keys:
		if verbose >= 3:
			sys.stderr.write('Looking at keyno %d of %d\n' % (keyno, numkeys))
		keyno += 1
		if string.atoi(db[k]) > 0:
			should_we_continue(db)
#			if verbose >= 3:
#				sys.stderr.write('File %s has not succeeded or failed yet\n' % k)
			srcfile=myjoin(srcdir,k)
			dstfile=myjoin(dstdir,k)
			try:
				srcsb=os.lstat(srcfile)
			except exceptions, (errno, detail):
				sys.stderr.write('Woah...  Source file %s does not exist?  Decrementing!: %d %s\n' % (srcfile, errno, detail))
				db[k] = decrement(db[k])
				srcsb=None
				continue
			else:
				if verbose >= 2:
					sys.stderr.write('lstat of source file %s succeeded\n' % srcfile)
			try:
				dstsb=os.lstat(dstfile)
			except exceptions, (errno, detail):
				dstsb=None
				if verbose >= 2:
					sys.stderr.write("Destination file %s not stat'able, but that's at least semi-normal: %d %s\n" % (dstfile, errno, detail))
			else:
				if verbose >= 2:
					sys.stderr.write('lstat of destination file %s succeeded\n' % dstfile)
			if stat.S_ISDIR(srcsb[stat.ST_MODE]):
				handle_dir(db,k,srcfile,srcsb,dstfile,dstsb)
			elif stat.S_ISREG(srcsb[stat.ST_MODE]):
				handle_regular(db,k,srcfile,srcsb,dstfile,dstsb)
			elif stat.S_ISLNK(srcsb[stat.ST_MODE]):
				handle_symlink(db,k,srcfile,srcsb,dstfile,dstsb)
			else:
				sys.stderr.write('Erroring out on source file %s: It is not a directory, link or regular file\n' % srcfile)
				sys.stderr.write('Transfer terminated prematurely!\n')
				sys.exit(1)
	perform_summarization(db)

dstdir=srcdir=''
verbose=init=erase=restart=manual=get=summarize=0

succeeded = "-10"
failed = "-20"

files_done = 0L

too_many_consecutive_errors = 0
consecutive_errors_seen = 0
continuetest_every = 0

exceptions = (IOError, OSError)
#except IOError, (errno, detail)
#except exceptions, (errno, detail):

while sys.argv[1:]:
	if sys.argv[3:] and sys.argv[1] == '-i':
		init = 1
		database=sys.argv[2]
		repetitions=string.atoi(sys.argv[3])
		if repetitions < 1:
			sys.stderr.write('Repetitions must be >= 1\n')
			usage()
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[2:] and sys.argv[1] == '-e':
		erase = 1
		database=sys.argv[2]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[2:] and sys.argv[1] == '-s':
		summarize = 1
		database=sys.argv[2]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-d':
		srcdir = sys.argv[2]
		dstdir = sys.argv[3]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-r':
		restart = 1
		database=sys.argv[2]
		repetitions=string.atoi(sys.argv[3])
		if repetitions < 1:
			sys.stderr.write('Repetitions must be >= 1\n')
			usage()
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-c':
		continuetest=sys.argv[2]
		continuetest_every=string.atoi(sys.argv[3])
		if continuetest_every < 1:
			sys.stderr.write('second argument to -c skip must be >= 1\n')
			usage()
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-m':
		manual = 1
		database = sys.argv[2]
		repetitions=string.atoi(sys.argv[3])
		filename = sys.argv[4]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[3:] and sys.argv[1] == '-g':
		get = 1
		database = sys.argv[2]
		filename = sys.argv[3]
		del sys.argv[1]
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[1:] and sys.argv[1] == '-v':
		verbose = string.atoi(sys.argv[2])
		del sys.argv[1]
		del sys.argv[1]
	elif sys.argv[1:] and sys.argv[1] == '-C':
		too_many_consecutive_errors = string.atoi(sys.argv[2])
		del sys.argv[1]
		del sys.argv[1]
	else:
		usage()

# these should all be 0 or 1, and they are mutually exclusive, so their
# sum must be exactly 1, no less, no more
if init + erase + restart + manual + get + summarize != 1:
	sys.stderr.write('%s: -i, -r, -m, -g, -s and -e are mutually exclusive, and exactly one must be specified\n' % sys.argv[0])
	usage()

if (init or restart) and (srcdir == '' or dstdir == ''):
	sys.stderr.write('-i and -r both require -d\n')
	usage()
	
if manual:
	sys.stderr.write('Manually setting %s to %d in %s\n' % (filename, repetitions, database))
	try:
		db = anydbm.open(database,'r')
		db.close()
	except exceptions, (errno, detail):
		sys.stderr.wrtite('Sorry, database %s does not exist.  Please use -i: %d %s\n' % (database, errno, detail))
		sys.exit(1)
	db = anydbm.open(database,'w')
	db[filename] = str(repetitions)
	db.close()
	sys.exit(0)

if manual:
	sys.stderr.write('Manually setting %s to %d in %s\n' % (filename, repetitions, database))
	try:
		db = anydbm.open(database,'r')
		db.close()
	except exceptions, (errno, detail):
		sys.stderr.wrtite('Sorry, database %s does not exist.  Please use -i: %d %s\n' % (database, errno, detail))
		sys.exit(1)
	db = anydbm.open(database,'w')
	db[filename] = str(repetitions)
	db.close()
	sys.exit(0)

if get:
	sys.stderr.write('Retrieving count for file %s in %s....\n' % (filename, database))
	db = anydbm.open(database,'r')
	if db.has_key(filename):
		value = db[filename]
	else:
		sys.stderr.write('Sorry, database %s does not have file %s in it\n' % (database, filename))
		sys.exit(1)
	if value == succeeded:
		sys.stdout.write('Value is: succeeded (%s is the corresponding sentinel value)\n' % value)
	elif value == failed:
		sys.stdout.write('Value is: failed (%s is the corresponding sentinel value)\n' % value)
	else:
		sys.stdout.write('Value is: %s repetitions remaining\n' % value)
	#sys.stdout.write('%s\n' % db[filename])
	db.close()
	sys.exit(0)

if init:
	# if the database preexists, error out
	try:
		db = anydbm.open(database,'r')
		db.close()
	except (anydbm.error):
		if verbose >= 2:
			sys.stderr.write('Database nonexistent, but that is normal\n')
		pass
	else:
		sys.stderr.write('When using -i, the database must not preexist.  Please use -e\n')
		usage()
	sys.stderr.write('Initializing....\n')
	db = anydbm.open(database,'c')
	os.chdir(srcdir)
	traverse(db,repetitions,initial=1)
	count_down(db,srcdir,dstdir)
	db.close()
	sys.exit(0)

if restart:
	# just check to be sure it already exists
	try:
		db = anydbm.open(database,'r')
		db.close()
	except (anydbm.error):
		sys.stderr.write('Sorry, database %s does not exist.  Please use -i\n')
		sys.exit(1)
	db = anydbm.open(database,'w')
	os.chdir(srcdir)
	traverse(db,repetitions,initial=0)
	count_down(db,srcdir,dstdir)
	db.close()
	sys.exit(0)

if summarize:
	# just check to be sure it already exists
	try:
		db = anydbm.open(database,'r')
	except (anydbm.error):
		sys.stderr.write('Sorry, database %s does not exist.  Please use -i\n')
		sys.exit(1)
	perform_summarization(db)
	db.close()
	sys.exit(0)


