#!/usr/bin/env python ## @file csbackchk.py # @brief Check checksums or rather integrity of corresponding files. # # ----------------------------------------------------------------------------- # # $Id$ # @author Daniel Armbruster # \date 03/01/2012 # # Purpose: Check checksums or rather integrity of corresponding files. # # ---- # This file is part of csback. # # csback is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # csback is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with csback. If not, see . # ---- # # Copyright (c) 2012 by Daniel Armbruster # # REVISIONS and CHANGES # 03/01/2012 V0.1 Daniel Armbruster # 10/01/2012 V0.2 adjustments because every subdirectory now contains its own # checksumfile. # 11/01/2012 V0.4 pid lockfile handler ability added # # ============================================================================= import getopt import os import sys import pwd import logging import csfile import csbacklog import pidlock __version__ = "V0.2" __subversion__ = "$Id$" __license__ = "GPLv2" __author__ = "Daniel Armbruster" __copyright__ = "Copyright (c) 2012 by Daniel Armbruster" # ----------------------------------------------------------------------------- class Error(Exception): def __init__(self, line, msg): self.line = str(line) self.msg = msg def display(self): sys.stderr.write("csbackchk (ERROR): " + self.msg + "\n") sys.stderr.write("triggered in line: " + self.line + "\n") class Usage(Error): def display(self): usage_text = "Version: "+__version__+"\nLicense: "+__license__+ \ "\n"+__subversion__+"\nAuthor: "+__author__+ """ Usage: csbackchk [-v|--verbose] [-e REGEX [-e REGEX [...]]] [-R|--notrecursive] [-d|--debug] [-f|--followlinks] [-t|--tolerant] [-l|--logging] [-L|--lock] [SOURCEPATH] PATH or: csbackchk -h|--help\n""" sys.stderr.write("csbackchk [line "+self.line+"]: "+self.msg+"\n") sys.stderr.write(usage_text) # ----------------------------------------------------------------------------- def help(): """ Printing helptext to stdout. """ help_text = "Version: "+__version__+"\nLicense: "+__license__+"\n"+ \ __subversion__+"\nAuthor: "+__author__+""" Usage: csbackchk [-v|--verbose] [-e REGEX [-e REGEX [...]]] [-R|--notrecursive] [-d|--debug] [-f|--followlinks] [-t|--tolerant] [-l|--logging] [-L|--lock] [SOURCEPATH] PATH or: csbackchk -h|--help ------------------------------------------------------------------------------- -v|--verbose Be verbose. -h|--help Display this help. -e REGEX While checking a checksumfile(s) exclude files and directories matching REGEX(s). -R|--notrecursive Do not search in subdirectories of PATH. -d|--debug Debug mode. Be really verbose. -f|--followlinks Follow symbolic links. Only available if option -R is not set. Note that this option can lead to infinite recursion. -t|--tolerant Be tolerant. While checking don't report anything if a file listed in the checksumfile is missing. This flag is useful if a test of a directory is executed but this directory is realized e.g. as a ring buffer. -l|--logging Switch on logging to files. Logfiles will be located in ~/.csback/log/. -L|--lock Lock the directories working at. This flag is useful in case csbackchk was run simultaneously with other csback processes working in the same directory. Setting this option avoids checksumfile access problems which might occur. SOURCEPATH Optional sourcepath for comparison with files backed up in PATH. PATH and its subdirectories (if option '-R' had not been selected) must contain the csback checksumfile(s). Note that the directory structure (if option '-R' is not set) bellow SOURCEPATH should be equal to those in PATH because otherwise the files won't be found. If SOURCEPATH is not passed a check of files located in PATH with its checksumfiles will be performed. PATH Path to perform check with its checksumfile(s). If option '-R' had not been set the check will be performed additionally for PATHs' subdirectories. """ sys.stdout.write(help_text) # ----------------------------------------------------------------------------- def main(argv=None): # configure logger logger = csbacklog.CsbackLog('csbackchk') console = logging.StreamHandler() console.setFormatter(logging.Formatter( \ '%(name)-8s [%(lineno)d]: %(levelname)-8s %(message)s')) # fetch arguments if argv is None: argv = sys.argv try: try: opts, args = getopt.getopt(argv[1:], "vhe:RdftlL", ["help", "verbose", \ "notrecursive", "debug", "followlinks", "tolerant", "logging", \ "lock"]) except getopt.GetoptError as err: raise Usage(143,err.msg) verbose = False debugMode = False notRecursive = False followLinks = False beTolerant = False regexes = [] pidLock = False # collect arguments for opt, arg in opts: if opt in ("-v", "--verbose"): verbose = True console.setLevel(logging.INFO) elif opt in ("-h", "--help"): sys.exit(help()) return 0 elif opt in ("-R", "--notrecursive"): notRecursive = True elif opt in ("-e"): regexes.append(arg) elif opt in ("-d", "--debug"): debugMode = True console.setLevel(logging.DEBUG) elif opt in ("-f", "--followlinks"): followLinks = True elif opt in ("-t", "--tolerant"): beTolerant = True elif opt in ("-l", "--logging"): logger.configure() elif opt in ("-L", "--lock"): pidLock = True else: raise Usage(175,"Unhandled option chosen.") if verbose or debugMode: logger.addHandler(console) if 1 == len(args) or 2 == len(args) and \ args[0].rstrip(os.sep)+os.sep == args[1].rstrip(os.sep)+os.sep: sourcepath = str(args[0]).rstrip(os.sep)+os.sep sourceDirs = [sourcepath] inputpath = sourcepath inputDirs = sourceDirs elif 2 == len(args): sourcepath = str(args[0]).rstrip(os.sep)+os.sep sourceDirs = [sourcepath] inputpath = str(args[1]).rstrip(os.sep)+os.sep inputDirs = [inputpath] else: raise Usage(192,"Invalid argument(s).") # major part logger.getLogger().debug("Collecting subdirectories ...") # recursive if not notRecursive: sourceDirs.extend( \ csfile.getSubDirectories(sourcepath, regexes, followLinks)) if sourceDirs[0] != inputDirs[0]: inputDirs.extend( \ csfile.getSubDirectories(inputpath, regexes, followLinks)) if len(sourceDirs) != len(inputDirs): raise Error(205, \ "Directory structure of inputpath and sourcepath different.") paths = list(zip(inputDirs, sourceDirs)) logger.getLogger().info("Start checking checksums ...") for path in paths: if not csfile.hasCsFile(path[0]): raise Error(213,"PATH does not contain a checksumfile.") lock = pidlock.PidLocker(path[0]) checksumfile = csfile.CsFile(path[0], path[1]) if lock.lockValid(): raise pidlock.PidLockError(-1, \ "Directory '{0}' locked.".format(path[0])) elif pidLock: lock.announce(os.getpid()) checksumfile.read() checksumfile.check(regexes, beTolerant) lock.cancel(os.getpid()) else: checksumfile.read() checksumfile.check(regexes, beTolerant) except Usage as err: err.display() return 2 except pidlock.PidLockError as err: logger.getLogger().error("{0}".format(err.msg)) err.display() return 2 except Error as err: logger.getLogger().error("message: %s [line %s]", err.msg, err.line) err.display() return 2 except csfile.CsFileError as err: logger.getLogger().error("message: %s [line %s]", err.msg, err.line) err.display() return 2 else: logger.getLogger().info("Checks performed.") return 0 # ----------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(main()) # ----- END OF csbackchk.py -----