csbackchk.py 8.9 KB
Newer Older
1
2
#!/usr/bin/env python
## @file csbackchk.py
3
# @brief Check checksums or rather integrity of corresponding files. 
4
5
6
7
8
# 
# -----------------------------------------------------------------------------
# 
# $Id$
# @author Daniel Armbruster
9
# \date 03/01/2012
10
# 
11
# Purpose: Check checksums or rather integrity of corresponding files.
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#
# ----
# 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 <http://www.gnu.org/licenses/>.
# ----
# 
30
# Copyright (c) 2012 by Daniel Armbruster
31
32
# 
# REVISIONS and CHANGES 
33
# 03/01/2012  V0.1  Daniel Armbruster
34
35
# 10/01/2012  V0.2  adjustments because every subdirectory now contains its own
#                   checksumfile.
36
# 11/01/2012  V0.4  pid lockfile handler ability added
37
38
39
# 
# =============================================================================
 
40
41
42
43
44
45
import getopt
import os
import sys
import pwd
import logging
import csfile
46
import csbacklog
47
import pidlock
48

49
__version__ = "V0.2"
50
51
52
53
__subversion__ = "$Id$"
__license__ = "GPLv2"
__author__ = "Daniel Armbruster"
__copyright__ = "Copyright (c) 2012 by Daniel Armbruster"
54

55
56
57
# -----------------------------------------------------------------------------
class Error(Exception):

58
59
  def __init__(self, line, msg):
    self.line = str(line)
60
61
62
63
    self.msg = msg

  def display(self):
    sys.stderr.write("csbackchk (ERROR): " + self.msg + "\n")
64
    sys.stderr.write("triggered in line: " + self.line + "\n")
65
66
67
68
69
70
71
72
73


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] 
74
                  [-t|--tolerant] [-l|--logging] [-L|--lock] [SOURCEPATH] PATH
75
    or: csbackchk -h|--help\n"""
76
    sys.stderr.write("csbackchk [line "+self.line+"]: "+self.msg+"\n")
77
78
79
80
81
82
83
84
85
86
87
    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]
88
                  [-t|--tolerant] [-l|--logging] [-L|--lock] [SOURCEPATH] PATH
89
90
91
92
    or: csbackchk -h|--help
-------------------------------------------------------------------------------
 -v|--verbose       Be verbose.
 -h|--help          Display this help.
93
94
 -e REGEX           While checking a checksumfile(s) exclude files and
                    directories matching REGEX(s).
95
96
97
98
99
100
101
102
103
 -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.
104
105
 -l|--logging       Switch on logging to files. Logfiles will be located in
                    ~/.csback/log/.
106
107
108
109
 -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
110
                    occur.
111
 SOURCEPATH         Optional sourcepath for comparison with files backed up in
112
113
                    PATH. PATH and its subdirectories (if option '-R' had not
                    been selected) must contain the csback checksumfile(s).
114
                    Note that the directory structure (if option '-R' is not
115
                    set) bellow SOURCEPATH should be equal to those in PATH
116
                    because otherwise the files won't be found.
117
118
                    If SOURCEPATH is not passed a check of files located in
                    PATH with its checksumfiles will be performed.
119
120
121
 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.
122
123
"""
  sys.stdout.write(help_text)
124
125

# -----------------------------------------------------------------------------
126
127
def main(argv=None):
  # configure logger
128
129
  logger = csbacklog.CsbackLog('csbackchk')
  
130
  console = logging.StreamHandler()
131
132
  console.setFormatter(logging.Formatter( \
    '%(name)-8s [%(lineno)d]: %(levelname)-8s %(message)s'))
133

134
  # fetch arguments
135
136
137
138
  if argv is None:
    argv = sys.argv
  try:
    try:
139
      opts, args = getopt.getopt(argv[1:], "vhe:RdftlL", ["help", "verbose", \
140
        "notrecursive", "debug", "followlinks", "tolerant", "logging", \
141
        "lock"])
142
    except getopt.GetoptError as err:
143
      raise Usage(143,err.msg)
144
145
146
147
148
149
    verbose = False
    debugMode = False
    notRecursive = False
    followLinks = False
    beTolerant = False
    regexes = []
150
    pidLock = False
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    # 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
170
171
      elif opt in ("-l", "--logging"):
        logger.configure()
172
173
      elif opt in ("-L", "--lock"):
        pidLock = True
174
      else:
175
        raise Usage(175,"Unhandled option chosen.")
176
177

    if verbose or debugMode:
178
      logger.addHandler(console)
179

Daniel Armbruster's avatar
Daniel Armbruster committed
180
181
    if 1 == len(args) or 2 == len(args) and \
      args[0].rstrip(os.sep)+os.sep == args[1].rstrip(os.sep)+os.sep:
182
      sourcepath = str(args[0]).rstrip(os.sep)+os.sep
183
      sourceDirs = [sourcepath]
184
      inputpath = sourcepath 
185
      inputDirs = sourceDirs
186
187
    elif 2 == len(args):
      sourcepath = str(args[0]).rstrip(os.sep)+os.sep 
188
      sourceDirs = [sourcepath] 
189
      inputpath = str(args[1]).rstrip(os.sep)+os.sep 
190
      inputDirs = [inputpath] 
191
    else:
192
      raise Usage(192,"Invalid argument(s).")
193
194

    # major part
195
196
197
198
199
200
201
202
203
204
    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):
205
      raise Error(205, \
206
207
208
209
210
211
        "Directory structure of inputpath and sourcepath different.")
      
    paths = list(zip(inputDirs, sourceDirs))
  
    logger.getLogger().info("Start checking checksums ...")
    for path in paths:
212
213
214
      if not csfile.hasCsFile(path[0]):
        raise Error(213,"PATH does not contain a checksumfile.")
      lock = pidlock.PidLocker(path[0])
215
      checksumfile = csfile.CsFile(path[0], path[1])
216
217
218
219
220
221
222
223
224
225
226
227
      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)

228
229
230
  except Usage as err:
    err.display()
    return 2
231
232
233
234
  except pidlock.PidLockError as err:
    logger.getLogger().error("{0}".format(err.msg))
    err.display()
    return 2
235
  except Error as err:
236
    logger.getLogger().error("message: %s [line %s]", err.msg, err.line)
237
238
    err.display()
    return 2
239
  except csfile.CsFileError as err:
240
    logger.getLogger().error("message: %s [line %s]", err.msg, err.line)
241
242
243
    err.display()
    return 2
  else:
244
    logger.getLogger().info("Checks performed.")
245
246
247
248
249
250
251
    return 0
    
# -----------------------------------------------------------------------------
if __name__ == "__main__":
  sys.exit(main())

# ----- END OF csbackchk.py -----