Commits

George Notaras  committed 98f8982

Update code to trunk

  • Participants
  • Parent commits 31cf5d5

Comments (0)

Files changed (13)

 VeriTAR ChangeLog
 
+== 0.4.0 -> 0.5.0 ==
+
+ * Added the VeriTAR package.
+
 == 0.3.0 -> 0.4.0 ==
 
  * Improved exception handling.
 include README
 include setup.cfg
 include setup.py
-include veritar
-include VeriTAR.py
-include sigtar
-include SigTAR.py
+include scripts/veritar
+include scripts/sigtar
+recursive-include VeriTAR *.py
 

File SigTAR.py

-# -*- coding: utf-8 -*-
-#
-#  SigTAR: Creates a TAR archive and a file containing the md5 sums of each of
-#          the archive's contents.
-#
-#  Project: https://www.codetrax.org/projects/veritar
-#
-#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-
-import sys
-import os
-import time
-import subprocess
-import hashlib
-from optparse import OptionParser
-
-__version__ = '0.1.0'
-
-
-def main():
-
-	# Files and directories to be archived
-	opts, args = parse_cli()
-	
-	# Set global variable VERBOSE
-	global VERBOSE
-	VERBOSE = opts.verbose
-	
-	progname = os.path.basename(sys.argv[0])
-	log('Using %s v%s\n' % (progname, __version__))
-	
-	# Create the archive
-	create_archive(opts.archfile, args)
-
-
-
-def parse_cli():
-	usage = '%prog [options] file1 file2 dir1 file3 ...'
-	parser = OptionParser(usage=usage, version=__version__)
-	parser.add_option('-f', '--file', action='store', type='string', dest='archfile', metavar='path',
-		help='The path to the TAR file that will be created. This is a mandatory option.')
-	parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
-		help= 'Print verbose messages to stdout. If not used, no informational messages will be printed, except errors.')
-	opts, args = parser.parse_args()
-	if not args:
-		parser.error('At least one file or directory must be passed as an argument.')
-	elif not opts.archfile:
-		parser.error('A filename for the archive has not been specified. Use the -f option.')
-	return opts, args
-
-
-
-def create_archive(tar_file_path, args):
-	
-	# Set the path of the file containing the md5 sums
-	md5_file_path = '%s.md5' % tar_file_path
-	
-	TAR_ARGS = [
-		'tar',
-		'-cvf',
-		tar_file_path,
-		]
-	
-	# Add files and dirs to be archived
-	TAR_ARGS.extend(args)
-	
-	# Run tar
-	p = subprocess.Popen(TAR_ARGS, stdout=subprocess.PIPE)
-	
-	# Open archive and md5file
-	f_md5 = open(md5_file_path, 'w')
-	
-	for line in p.stdout:
-		line = line.strip()
-		
-		log("==> Added: '%s'" % line)
-		
-		if os.path.isfile(line):
-			log("~~> Writing md5 sum for regular file: '%s'" % line)
-			f_md5.write('%s  %s\n' % (file_md5sum(line), line))
-	
-	f_md5.close()
-
-
-def file_md5sum(path):
-	"""Returns the md5 sum of the file at 'path'.
-	
-	Accepts a path to a file.
-	"""
-	# Only regular files are accepted
-	if not os.path.isfile(path):
-		return
-	f = open(path, 'rb')
-	READ_BLOCK_SIZE = 10240
-	m = hashlib.md5()
-	data = f.read(READ_BLOCK_SIZE)
-	while data:
-		m.update(data)
-		data = f.read(READ_BLOCK_SIZE)
-	f.close()
-	return m.hexdigest()
-
-
-#
-# Logging
-#
-
-def log(msg):
-	if VERBOSE:
-		sys.stdout.write('%s\n' % msg)
-		sys.stdout.flush()
-
-def error(msg):
-	sys.stderr.write('ERROR: %s\n' % msg)
-	sys.stderr.flush()
-
-def warning(msg):
-	sys.stderr.write('WARNING: %s\n' % msg)
-	sys.stderr.flush()
-
-
-if __name__ == '__main__':
-	main()
-
-
-
-

File VeriTAR.py

-# -*- coding: utf-8 -*-
-#
-#  VeriTAR: In-place verification of the MD5 sums of files within a tar archive.
-#
-#  Project: https://www.codetrax.org/projects/veritar
-#
-#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-
-__version__ = "0.4.0"
-__author__ = "George Notaras <gnot -at- g-loaded.eu>"
-__credits__ = ""
-
-import os
-import sys
-import tarfile
-#import hashlib
-import md5	# for compatibility with older Python versions
-from optparse import OptionParser
-import time
-
-
-def err(msg, opts=None):
-	"""Writes 'msg' to stderr.
-	"""
-	if not opts or not opts.quiet:
-		sys.stderr.write("%s\n" % msg)
-		sys.stderr.flush()
-
-def warn(msg, opts):
-	"""Writes 'msg' to stderr.
-	"""
-	if not opts.quiet:
-		if not opts.nowarn:
-			sys.stderr.write("%s\n" % msg)
-			sys.stderr.flush()
-
-def info(msg, opts, force=False):
-	"""Writes 'msg' to stdout if verbose.
-	
-	If force is True message is printed to stdout regardless of verbosity
-	level.
-	"""
-	if opts.verbose or force:
-		sys.stdout.write("%s\n" % msg)
-		sys.stdout.flush()
-
-def get_member_md5sum(f):
-	"""Returns the archived file's md5 sum.
-	
-	Accepts the file descriptor of the tar member.
-	"""
-	READ_BLOCK_SIZE = 65536
-	def read_block():
-		try:
-			data_block = f.read(READ_BLOCK_SIZE)
-		except IOError:
-			raise IOError("Corrupted Member")
-		else:
-			return data_block
-	#m = hashlib.md5()
-	m = md5.new()
-	data = read_block()
-	while data:
-		m.update(data)
-		data = read_block()
-	return m.hexdigest()
-
-def get_valid_checksums(path):
-	"""Returns a dictionary of items: 'name:md5sum'
-	
-	Reads path and retrieves md5 sums and pathnames, which are stored in a
-	dictionary using the format:   'name' : md5sum
-	"""
-	csums = {}
-	f_sums = open(path)
-	for line in f_sums:
-		if line.strip():
-			try:
-				csum, name = line.split("  ")
-			except ValueError:
-				err("ERROR: bad checksum file: %s" % path)
-				sys.exit(1)
-			else:
-				csums[name.strip()] = csum.strip()
-	f_sums.close()
-	return csums
-
-
-class Stats:
-	"""Verification statistics object
-	
-	Holds counters.
-	Provides functions that increase each counter and also print messages.
-	"""
-	L_JUST = 10	# Left justify position
-	
-	# Counters
-	Processed = 0
-	Good = 0
-	Skipped = 0
-	Corrupted = 0
-	Missing = 0
-	
-	def __init__(self, opts):
-		self.opts = opts
-	
-	def IncProcessed(self):
-		"""Increases the 'Processed' counter.
-		
-		Types of files that this counter counts:
-			- Verified
-			- Skipped
-			- Failed
-		
-		It does not count the 'missing'
-		"""
-		self.Processed += 1
-	
-	def IncGood(self, name):
-		"""Increases the 'Good' counter.
-		
-		For verified files.
-		"""
-		info("%s %s" % ("OK".ljust(self.L_JUST), name), self.opts)
-		self.Good += 1
-	
-	def IncSkipped(self, name, mtype):
-		"""Increases the Skipped counter.
-		
-		'Skipped' are tar members which are not regular files.
-		It does not matter whether a checksum for a non-regular file
-		exists. It is not an error.
-		"""
-		warn("%s SKIPPING: %s (%s)" % ("WARNING".ljust(self.L_JUST), \
-						name, mtype), self.opts)
-		self.Skipped += 1
-	
-	def IncCorrupted(self, name):
-		err("%s %s" % ("CORRUPT".ljust(self.L_JUST), name), self.opts)
-		self.Corrupted += 1
-	
-	def IncMissing(self, name):
-		# Tar member exists, but no checksum
-		# Not a TAR integrity error,
-		# but md5sum file is bad
-		warn("%s MISSING: %s" % ("WARNING".ljust(self.L_JUST), \
-							name), self.opts)
-		self.Missing += 1
-
-	def summary(self):
-		ssw = sys.stdout.write
-		title = "TAR members checksum verification"
-		ssw("\n%s\n" % title)
-		ssw("%s\n" % ("-" * len(title)))
-		ssw("%s: %s\n" % ("Processed".ljust(self.L_JUST), self.Processed))
-		ssw("%s: %s\n" % ("Verified".ljust(self.L_JUST), self.Good))
-		ssw("%s: %s\n" % ("Skipped".ljust(self.L_JUST), self.Skipped))
-		ssw("%s: %s\n" % ("Failed".ljust(self.L_JUST), self.Corrupted))
-		ssw("%s: %s\n" % ("Missing".ljust(self.L_JUST), self.Missing))
-		ssw("%s\n" % ("-" * len(title)))
-		if self.Corrupted:
-			ssw("FAILED")
-		elif self.Missing:
-			ssw("MISSING CHECKSUMS, TAR INTEGRITY OK SO FAR")
-		else:
-			ssw("SUCCESS")
-		ssw("\n%s\n" % ("-" * len(title)))
-		sys.stdout.flush()
-
-
-
-class TarVerification:
-
-	TAR_IGNORE_ZEROS = True
-	TAR_DEBUG = 1
-	TAR_ERRORLEVEL = 2
-	
-	def __init__(self, tarpath, checksumpath, opts):
-		"""Constructs the verifier object.
-		
-		Accepts:
-			tarpath		: path to TAR archive
-			checksumpath	: path to file with md5  checksums
-			opts		: options
-		
-		self.csums : Dictionary with 'filename:md5sum'
-		self.f_tar : TAR archive file object
-		self.opts  : options
-		self.s	   : statistics object
-		"""
-		self.csums = get_valid_checksums(checksumpath)
-		self.f_tar = self.__open_archive(tarpath)
-		self.type_translator = self.__get_supported_types_dict()
-		self.s = Stats(opts)
-		info("%s v%s\n\n" % \
-		(os.path.basename(sys.argv[0]), __version__), opts, force=True)
-		if opts.quiet:
-			info("please wait...\n", opts, force=True)
-	
-	def __open_archive(self, tarpath):
-		try:
-			f_tar = tarfile.open(tarpath, "r|*")
-		except tarfile.ReadError, e:
-			err("ERROR: %s: %s" % (str(e)[:str(e).find(":")], tarpath))
-			sys.exit(1)
-		else:
-			f_tar.ignore_zeros = self.TAR_IGNORE_ZEROS
-			f_tar.debug = self.TAR_DEBUG
-			f_tar.errorlevel = self.TAR_ERRORLEVEL
-			return f_tar
-	
-	def __close_archive(self):
-		self.f_tar.close()
-	
-	def __member_md5sum(self, member):
-		f = self.f_tar.extractfile(member)
-		try:
-			checksum = get_member_md5sum(f)
-		except IOError:
-			f.close()
-			return None
-		else:
-			f.close()
-			return checksum
-
-	def __get_supported_types_dict(self):
-		type_translator = {}
-		for t_attrib in dir(tarfile):
-			if t_attrib.find("TYPE") != -1:
-				if t_attrib not in ("REGULAR_TYPES", "SUPPORTED_TYPES"):
-					if not type_translator.has_key(t_attrib):
-						type_translator[getattr(tarfile, t_attrib)] = t_attrib
-		return type_translator
-	
-	def __check_member(self, member):
-		"""Checks one member
-		"""
-		self.s.IncProcessed()
-		if member.isfile():
-			if self.csums.has_key(member.name):
-				checksum = self.__member_md5sum(member)
-				if checksum == self.csums[member.name]:
-					self.s.IncGood(member.name)
-				else:
-					self.s.IncCorrupted(member.name)
-				del self.csums[member.name]
-			else:
-				self.s.IncMissing(member.name)
-		else:
-			if self.csums.has_key(member.name):
-				del self.csums[member.name]
-			self.s.IncSkipped(member.name, self.type_translator[member.type])
-	
-	def __process_remnants(self):
-		"""If the checksums-file contains more items than the TAR
-		members it is assumed that the archive is corrupted.
-		"""
-		if self.csums:
-			for remnant in self.csums.keys():
-				self.s.IncProcessed()
-				self.s.IncCorrupted(remnant)
-	def run(self):
-		while True:
-			try:
-				member = self.f_tar.next()
-			except IOError:
-				# Counted in __process_remnants()
-				continue
-			else:
-				if not member:
-					break
-				else:
-					self.__check_member(member)
-		self.__process_remnants()
-		self.__close_archive()
-		self.s.summary()
-
-
-
-def parse_cli():
-	usage = """
-
-    %prog [options] tar_archive checksum_file
-
-    checksum_file format:  'md5sum  path'"""
-	parser = OptionParser(usage=usage, version=__version__)
-	parser.add_option("-v", "--verbose", action="store_true", \
-		dest="verbose", help= \
-		"""Print all messages. Cannot be used with -q.""")
-	parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
-		help="""Only checksum errors will be printed. Warnings are \
-suppressed. Cannot be used with -v.""")
-	parser.add_option("-n", "--no-warn", action="store_true", dest="nowarn",
-		help="""Warnings are suppressed. Note that using this switch \
-together with -q has absolutely no effect, since -q suppresses warnings \
-anyway.""")
-	opts, args = parser.parse_args()
-	if len(args) != 2:
-		parser.error("Wrong number of arguments")
-	elif opts.quiet and opts.verbose:
-		parser.error("Cannot use -v and -q together.")
-	elif not os.path.exists(args[0]):
-		parser.error("Invalid file path: %s" % args[0])
-	elif not os.path.exists(args[1]):
-		parser.error("Invalid file path: %s" % args[1])
-	return opts, args
-
-
-
-def main():
-	start = time.time()
-	opts, args = parse_cli()
-	tar_path, csum_path = args
-	try:
-		Verification = TarVerification(tar_path, csum_path, opts)
-		Verification.run()
-	except KeyboardInterrupt:
-		err("Operation aborted by user", opts)
-		sys.exit(1)
-	else:
-		finish = time.time()
-		sys.stdout.write("Elapsed: %.3f sec\n" % (finish -start))
-		sys.stdout.flush()
-
-
-
-if __name__ == '__main__':
-	main()
-

File scripts/sigtar

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  SigTAR: Creates a TAR archive and a file containing the md5 sums of each of
+#          the archive's contents.
+#
+#  Project: https://www.codetrax.org/projects/veritar
+#
+#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+import VeriTAR.SigTAR
+
+VeriTAR.SigTAR.main()
+

File scripts/veritar

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  VeriTAR: In-place verification of the MD5 sums of files within a tar archive.
+#
+#  Project: https://www.codetrax.org/projects/veritar
+#
+#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+import VeriTAR.VeriTAR
+
+VeriTAR.VeriTAR.main()
+
 #! /usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-#  VeriTAR: In-place verification of the MD5 sums of files within a tar archive.
+#  This file is part of veritar.
+#
+#  veritar - 
 #
 #  Project: https://www.codetrax.org/projects/veritar
 #
-#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
 #
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
 #    python setup.py install --prefix=/usr
 #
 
-#from distutils.core import setup
-from setuptools import setup
-from VeriTAR import __version__
 
-p_name = "veritar"
-p_version = __version__
-p_desc_short = "In-place verification of the MD5 sums of files within a tar archive"
-p_desc_long = """VeriTAR description
-VeriTAR [Veri(fy)TAR] is a command-line utility that verifies the md5 sums of
-files within a TAR archive. Due to the tar ('ustar') format limitations the md5
-sums are retrieved from a separate file and are checked against the md5 sums of
-the files within the tar archive. The process takes place without actually
-exctracting the files.
-
-It works with corrupted tar archives. The program carries on to the next
-file within the archive skipping the damaged parts. At the moment, this relies 
-on Python's tarfile module internal functions.
-
-Compressed TAR archives (Gzip or BZ2) are supported.
-
-VeriTAR is written in Python.
-"""
-p_author = "George Notaras, G-Loaded.eu, CodeTRAX.org"
-p_author_email = "<gnot [at] g-loaded.eu>"
-p_url = "https://www.codetrax.org/projects/" + p_name
-p_download_url = "http://www.codetrax.org/downloads/projects/" + p_name + "/" + p_name + "-" + p_version + ".tar.gz"
-p_license = "Apache License version 2"
-p_classifiers = [
-	"Development Status :: 3 - Alpha",
-	"Environment :: Console",
-	"Intended Audience :: Information Technology",
-	"Intended Audience :: Developers",
-	"Intended Audience :: System Administrators",
-	"License :: OSI Approved :: Apache Software License",
-	"Natural Language :: English",
-	"Operating System :: OS Independent",
-	"Programming Language :: Python",
-	"Topic :: System :: Archiving",
-	"Topic :: Utilities",
-	]
+from distutils.core import setup
+#from setuptools import setup
+from VeriTAR import info
 
 if __name__=='__main__':
 	setup(
-		name = p_name,
-		version = p_version,
-		description = p_desc_short,
-		long_description = p_desc_long,
-		author = p_author,
-		author_email = p_author_email,
-		url = p_url,
-		download_url = p_download_url,
-		license = p_license,
-		classifiers = p_classifiers,
-		py_modules = ["VeriTAR", "SigTAR"],
-		scripts = ['veritar', 'sigtar']
+		name = info.name,
+		version = info.version,
+		description = info.description,
+		long_description = info.long_description,
+		author = info.author,
+		author_email = info.author_email,
+		url = info.url,
+		download_url = info.download_url,
+		license = info.license,
+		classifiers = info.classifiers,
+		packages = ['VeriTAR', ],
+		scripts = ['scripts/veritar', 'scripts/sigtar', ],
 		)
 

File sigtar

-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#  SigTAR: Creates a TAR archive and a file containing the md5 sums of each of
-#          the archive's contents.
-#
-#  Project: https://www.codetrax.org/projects/veritar
-#
-#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-
-import SigTAR
-
-SigTAR.main()
-

File veritar

-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#  VeriTAR: In-place verification of the MD5 sums of files within a tar archive.
-#
-#  Project: https://www.codetrax.org/projects/veritar
-#
-#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-
-import VeriTAR
-
-VeriTAR.main()
-

File veritar/SigTAR.py

+# -*- coding: utf-8 -*-
+#
+#  SigTAR: Creates a TAR archive and a file containing the md5 sums of each of
+#          the archive's contents.
+#
+#  Project: https://www.codetrax.org/projects/veritar
+#
+#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+import sys
+import os
+import time
+import subprocess
+import hashlib
+from optparse import OptionParser
+
+__version__ = '0.1.0'
+
+
+def main():
+
+	# Files and directories to be archived
+	opts, args = parse_cli()
+	
+	# Set global variable VERBOSE
+	global VERBOSE
+	VERBOSE = opts.verbose
+	
+	progname = os.path.basename(sys.argv[0])
+	log('Using %s v%s\n' % (progname, __version__))
+	
+	# Create the archive
+	create_archive(opts.archfile, args)
+
+
+
+def parse_cli():
+	usage = '%prog [options] file1 file2 dir1 file3 ...'
+	parser = OptionParser(usage=usage, version=__version__)
+	parser.add_option('-f', '--file', action='store', type='string', dest='archfile', metavar='path',
+		help='The path to the TAR file that will be created. This is a mandatory option.')
+	parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+		help= 'Print verbose messages to stdout. If not used, no informational messages will be printed, except errors.')
+	opts, args = parser.parse_args()
+	if not args:
+		parser.error('At least one file or directory must be passed as an argument.')
+	elif not opts.archfile:
+		parser.error('A filename for the archive has not been specified. Use the -f option.')
+	return opts, args
+
+
+
+def create_archive(tar_file_path, args):
+	
+	# Set the path of the file containing the md5 sums
+	md5_file_path = '%s.md5' % tar_file_path
+	
+	TAR_ARGS = [
+		'tar',
+		'-cvf',
+		tar_file_path,
+		]
+	
+	# Add files and dirs to be archived
+	TAR_ARGS.extend(args)
+	
+	# Run tar
+	p = subprocess.Popen(TAR_ARGS, stdout=subprocess.PIPE)
+	
+	# Open archive and md5file
+	f_md5 = open(md5_file_path, 'w')
+	
+	for line in p.stdout:
+		line = line.strip()
+		
+		log("==> Added: '%s'" % line)
+		
+		if os.path.isfile(line):
+			log("~~> Writing md5 sum for regular file: '%s'" % line)
+			f_md5.write('%s  %s\n' % (file_md5sum(line), line))
+	
+	f_md5.close()
+
+
+def file_md5sum(path):
+	"""Returns the md5 sum of the file at 'path'.
+	
+	Accepts a path to a file.
+	"""
+	# Only regular files are accepted
+	if not os.path.isfile(path):
+		return
+	f = open(path, 'rb')
+	READ_BLOCK_SIZE = 10240
+	m = hashlib.md5()
+	data = f.read(READ_BLOCK_SIZE)
+	while data:
+		m.update(data)
+		data = f.read(READ_BLOCK_SIZE)
+	f.close()
+	return m.hexdigest()
+
+
+#
+# Logging
+#
+
+def log(msg):
+	if VERBOSE:
+		sys.stdout.write('%s\n' % msg)
+		sys.stdout.flush()
+
+def error(msg):
+	sys.stderr.write('ERROR: %s\n' % msg)
+	sys.stderr.flush()
+
+def warning(msg):
+	sys.stderr.write('WARNING: %s\n' % msg)
+	sys.stderr.flush()
+
+
+if __name__ == '__main__':
+	main()
+
+
+
+

File veritar/VeriTAR.py

+# -*- coding: utf-8 -*-
+#
+#  VeriTAR: In-place verification of the MD5 sums of files within a tar archive.
+#
+#  Project: https://www.codetrax.org/projects/veritar
+#
+#  Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+__version__ = "0.4.0"
+__author__ = "George Notaras <gnot -at- g-loaded.eu>"
+__credits__ = ""
+
+import os
+import sys
+import tarfile
+#import hashlib
+import md5	# for compatibility with older Python versions
+from optparse import OptionParser
+import time
+
+
+def err(msg, opts=None):
+	"""Writes 'msg' to stderr.
+	"""
+	if not opts or not opts.quiet:
+		sys.stderr.write("%s\n" % msg)
+		sys.stderr.flush()
+
+def warn(msg, opts):
+	"""Writes 'msg' to stderr.
+	"""
+	if not opts.quiet:
+		if not opts.nowarn:
+			sys.stderr.write("%s\n" % msg)
+			sys.stderr.flush()
+
+def info(msg, opts, force=False):
+	"""Writes 'msg' to stdout if verbose.
+	
+	If force is True message is printed to stdout regardless of verbosity
+	level.
+	"""
+	if opts.verbose or force:
+		sys.stdout.write("%s\n" % msg)
+		sys.stdout.flush()
+
+def get_member_md5sum(f):
+	"""Returns the archived file's md5 sum.
+	
+	Accepts the file descriptor of the tar member.
+	"""
+	READ_BLOCK_SIZE = 65536
+	def read_block():
+		try:
+			data_block = f.read(READ_BLOCK_SIZE)
+		except IOError:
+			raise IOError("Corrupted Member")
+		else:
+			return data_block
+	#m = hashlib.md5()
+	m = md5.new()
+	data = read_block()
+	while data:
+		m.update(data)
+		data = read_block()
+	return m.hexdigest()
+
+def get_valid_checksums(path):
+	"""Returns a dictionary of items: 'name:md5sum'
+	
+	Reads path and retrieves md5 sums and pathnames, which are stored in a
+	dictionary using the format:   'name' : md5sum
+	"""
+	csums = {}
+	f_sums = open(path)
+	for line in f_sums:
+		if line.strip():
+			try:
+				csum, name = line.split("  ")
+			except ValueError:
+				err("ERROR: bad checksum file: %s" % path)
+				sys.exit(1)
+			else:
+				csums[name.strip()] = csum.strip()
+	f_sums.close()
+	return csums
+
+
+class Stats:
+	"""Verification statistics object
+	
+	Holds counters.
+	Provides functions that increase each counter and also print messages.
+	"""
+	L_JUST = 10	# Left justify position
+	
+	# Counters
+	Processed = 0
+	Good = 0
+	Skipped = 0
+	Corrupted = 0
+	Missing = 0
+	
+	def __init__(self, opts):
+		self.opts = opts
+	
+	def IncProcessed(self):
+		"""Increases the 'Processed' counter.
+		
+		Types of files that this counter counts:
+			- Verified
+			- Skipped
+			- Failed
+		
+		It does not count the 'missing'
+		"""
+		self.Processed += 1
+	
+	def IncGood(self, name):
+		"""Increases the 'Good' counter.
+		
+		For verified files.
+		"""
+		info("%s %s" % ("OK".ljust(self.L_JUST), name), self.opts)
+		self.Good += 1
+	
+	def IncSkipped(self, name, mtype):
+		"""Increases the Skipped counter.
+		
+		'Skipped' are tar members which are not regular files.
+		It does not matter whether a checksum for a non-regular file
+		exists. It is not an error.
+		"""
+		warn("%s SKIPPING: %s (%s)" % ("WARNING".ljust(self.L_JUST), \
+						name, mtype), self.opts)
+		self.Skipped += 1
+	
+	def IncCorrupted(self, name):
+		err("%s %s" % ("CORRUPT".ljust(self.L_JUST), name), self.opts)
+		self.Corrupted += 1
+	
+	def IncMissing(self, name):
+		# Tar member exists, but no checksum
+		# Not a TAR integrity error,
+		# but md5sum file is bad
+		warn("%s MISSING: %s" % ("WARNING".ljust(self.L_JUST), \
+							name), self.opts)
+		self.Missing += 1
+
+	def summary(self):
+		ssw = sys.stdout.write
+		title = "TAR members checksum verification"
+		ssw("\n%s\n" % title)
+		ssw("%s\n" % ("-" * len(title)))
+		ssw("%s: %s\n" % ("Processed".ljust(self.L_JUST), self.Processed))
+		ssw("%s: %s\n" % ("Verified".ljust(self.L_JUST), self.Good))
+		ssw("%s: %s\n" % ("Skipped".ljust(self.L_JUST), self.Skipped))
+		ssw("%s: %s\n" % ("Failed".ljust(self.L_JUST), self.Corrupted))
+		ssw("%s: %s\n" % ("Missing".ljust(self.L_JUST), self.Missing))
+		ssw("%s\n" % ("-" * len(title)))
+		if self.Corrupted:
+			ssw("FAILED")
+		elif self.Missing:
+			ssw("MISSING CHECKSUMS, TAR INTEGRITY OK SO FAR")
+		else:
+			ssw("SUCCESS")
+		ssw("\n%s\n" % ("-" * len(title)))
+		sys.stdout.flush()
+
+
+
+class TarVerification:
+
+	TAR_IGNORE_ZEROS = True
+	TAR_DEBUG = 1
+	TAR_ERRORLEVEL = 2
+	
+	def __init__(self, tarpath, checksumpath, opts):
+		"""Constructs the verifier object.
+		
+		Accepts:
+			tarpath		: path to TAR archive
+			checksumpath	: path to file with md5  checksums
+			opts		: options
+		
+		self.csums : Dictionary with 'filename:md5sum'
+		self.f_tar : TAR archive file object
+		self.opts  : options
+		self.s	   : statistics object
+		"""
+		self.csums = get_valid_checksums(checksumpath)
+		self.f_tar = self.__open_archive(tarpath)
+		self.type_translator = self.__get_supported_types_dict()
+		self.s = Stats(opts)
+		info("%s v%s\n\n" % \
+		(os.path.basename(sys.argv[0]), __version__), opts, force=True)
+		if opts.quiet:
+			info("please wait...\n", opts, force=True)
+	
+	def __open_archive(self, tarpath):
+		try:
+			f_tar = tarfile.open(tarpath, "r|*")
+		except tarfile.ReadError, e:
+			err("ERROR: %s: %s" % (str(e)[:str(e).find(":")], tarpath))
+			sys.exit(1)
+		else:
+			f_tar.ignore_zeros = self.TAR_IGNORE_ZEROS
+			f_tar.debug = self.TAR_DEBUG
+			f_tar.errorlevel = self.TAR_ERRORLEVEL
+			return f_tar
+	
+	def __close_archive(self):
+		self.f_tar.close()
+	
+	def __member_md5sum(self, member):
+		f = self.f_tar.extractfile(member)
+		try:
+			checksum = get_member_md5sum(f)
+		except IOError:
+			f.close()
+			return None
+		else:
+			f.close()
+			return checksum
+
+	def __get_supported_types_dict(self):
+		type_translator = {}
+		for t_attrib in dir(tarfile):
+			if t_attrib.find("TYPE") != -1:
+				if t_attrib not in ("REGULAR_TYPES", "SUPPORTED_TYPES"):
+					if not type_translator.has_key(t_attrib):
+						type_translator[getattr(tarfile, t_attrib)] = t_attrib
+		return type_translator
+	
+	def __check_member(self, member):
+		"""Checks one member
+		"""
+		self.s.IncProcessed()
+		if member.isfile():
+			if self.csums.has_key(member.name):
+				checksum = self.__member_md5sum(member)
+				if checksum == self.csums[member.name]:
+					self.s.IncGood(member.name)
+				else:
+					self.s.IncCorrupted(member.name)
+				del self.csums[member.name]
+			else:
+				self.s.IncMissing(member.name)
+		else:
+			if self.csums.has_key(member.name):
+				del self.csums[member.name]
+			self.s.IncSkipped(member.name, self.type_translator[member.type])
+	
+	def __process_remnants(self):
+		"""If the checksums-file contains more items than the TAR
+		members it is assumed that the archive is corrupted.
+		"""
+		if self.csums:
+			for remnant in self.csums.keys():
+				self.s.IncProcessed()
+				self.s.IncCorrupted(remnant)
+	def run(self):
+		while True:
+			try:
+				member = self.f_tar.next()
+			except IOError:
+				# Counted in __process_remnants()
+				continue
+			else:
+				if not member:
+					break
+				else:
+					self.__check_member(member)
+		self.__process_remnants()
+		self.__close_archive()
+		self.s.summary()
+
+
+
+def parse_cli():
+	usage = """
+
+    %prog [options] tar_archive checksum_file
+
+    checksum_file format:  'md5sum  path'"""
+	parser = OptionParser(usage=usage, version=__version__)
+	parser.add_option("-v", "--verbose", action="store_true", \
+		dest="verbose", help= \
+		"""Print all messages. Cannot be used with -q.""")
+	parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
+		help="""Only checksum errors will be printed. Warnings are \
+suppressed. Cannot be used with -v.""")
+	parser.add_option("-n", "--no-warn", action="store_true", dest="nowarn",
+		help="""Warnings are suppressed. Note that using this switch \
+together with -q has absolutely no effect, since -q suppresses warnings \
+anyway.""")
+	opts, args = parser.parse_args()
+	if len(args) != 2:
+		parser.error("Wrong number of arguments")
+	elif opts.quiet and opts.verbose:
+		parser.error("Cannot use -v and -q together.")
+	elif not os.path.exists(args[0]):
+		parser.error("Invalid file path: %s" % args[0])
+	elif not os.path.exists(args[1]):
+		parser.error("Invalid file path: %s" % args[1])
+	return opts, args
+
+
+
+def main():
+	start = time.time()
+	opts, args = parse_cli()
+	tar_path, csum_path = args
+	try:
+		Verification = TarVerification(tar_path, csum_path, opts)
+		Verification.run()
+	except KeyboardInterrupt:
+		err("Operation aborted by user", opts)
+		sys.exit(1)
+	else:
+		finish = time.time()
+		sys.stdout.write("Elapsed: %.3f sec\n" % (finish -start))
+		sys.stdout.flush()
+
+
+
+if __name__ == '__main__':
+	main()
+

File veritar/__init__.py

+# -*- coding: utf-8 -*-
+#
+#  This file is part of veritar.
+#
+#  veritar - 
+#
+#  Project: https://www.codetrax.org/projects/veritar
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+

File veritar/info.py

+# -*- coding: utf-8 -*-
+#
+#  This file is part of veritar.
+#
+#  veritar - 
+#
+#  Project: https://www.codetrax.org/projects/veritar
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+version = '0.5.0'
+status = 'alpha'
+name = 'veritar'
+description = """veritar"""
+long_description = """veritar"""
+author = 'George Notaras'
+author_email = 'gnot@g-loaded.eu'
+url = 'http://www.codetrax.org/projects/' + name
+download_url = "http://www.codetrax.org/downloads/projects/" + name + "/" + name + "-" + version + ".tar.gz"
+license = "Apache License version 2"
+
+# Automate the development status for classifiers
+devel_status = ''
+if status == 'pre-alpha':
+	devel_status = 'Development Status :: 2 - Pre-Alpha'
+if status == 'alpha':
+	devel_status = 'Development Status :: 3 - Alpha'
+if status == 'beta':
+	devel_status = 'Development Status :: 4 - Beta'
+if status == 'stable':
+	devel_status = 'Development Status :: 5 - Production/Stable'
+
+# For a list of classifiers check: http://www.python.org/pypi/
+# (http://pypi.python.org/pypi?:action=list_classifiers)
+
+classifiers = [
+	devel_status,
+	"Environment :: Console",
+	"Intended Audience :: Information Technology",
+	"Intended Audience :: Developers",
+	"Intended Audience :: System Administrators",
+	"License :: OSI Approved :: Apache Software License",
+	"Natural Language :: English",
+	"Operating System :: OS Independent",
+	"Programming Language :: Python",
+	"Topic :: System :: Archiving",
+	"Topic :: Utilities",
+	]
+
+def get_version():
+	return name + ' v' + version + '/' + status
+