"Fossies" - the Fresh Open Source Software Archive

Member "Tardis-1.2.1/src/Tardis/Regenerate.py" (9 Jun 2021, 26550 Bytes) of package /linux/privat/Tardis-1.2.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "Regenerate.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.1.5_vs_1.2.1.

    1 # vim: set et sw=4 sts=4 fileencoding=utf-8:
    2 #
    3 # Tardis: A Backup System
    4 # Copyright 2013-2020, Eric Koldinger, All Rights Reserved.
    5 # kolding@washington.edu
    6 #
    7 # Redistribution and use in source and binary forms, with or without
    8 # modification, are permitted provided that the following conditions are met:
    9 #
   10 #     * Redistributions of source code must retain the above copyright
   11 #       notice, this list of conditions and the following disclaimer.
   12 #     * Redistributions in binary form must reproduce the above copyright
   13 #       notice, this list of conditions and the following disclaimer in the
   14 #       documentation and/or other materials provided with the distribution.
   15 #     * Neither the name of the copyright holder nor the
   16 #       names of its contributors may be used to endorse or promote products
   17 #       derived from this software without specific prior written permission.
   18 #
   19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   20 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   21 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   22 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   23 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   24 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   25 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   26 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   27 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   28 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   29 # POSSIBILITY OF SUCH DAMAGE.
   30 
   31 import os
   32 import os.path
   33 import stat
   34 import sys
   35 import argparse
   36 import logging
   37 import time
   38 import base64
   39 import json
   40 import parsedatetime
   41 import xattr
   42 import hmac
   43 import posix1e
   44 
   45 import queue
   46 import threading
   47 
   48 import Tardis
   49 from Tardis import TardisDB
   50 from Tardis import Regenerator
   51 from Tardis import Util
   52 from Tardis import Config
   53 from Tardis import Defaults
   54 
   55 logger  = None
   56 crypt = None
   57 
   58 OW_NEVER  = 0
   59 OW_ALWAYS = 1
   60 OW_NEWER  = 2
   61 OW_OLDER  = 3
   62 OW_PROMPT = 4
   63 
   64 overwriteNames = { 'never': OW_NEVER, 'always': OW_ALWAYS, 'newer': OW_NEWER, 'older': OW_OLDER, 'ask': OW_PROMPT }
   65 if sys.stdout.isatty():
   66     owMode = OW_PROMPT
   67     owModeDefault = 'ask'
   68 else:
   69     owMode = OW_NEVER
   70     owModeDefault = 'never'
   71 
   72 errors = 0
   73 
   74 tardis = None
   75 args = None
   76 
   77 def yesOrNo(x):
   78     if x:
   79         x = x.strip().lower()
   80         return x[0] == 'y'
   81     else:
   82         return False
   83 
   84 def checkOverwrite(name, info):
   85     if os.path.exists(name):
   86         if owMode == OW_NEVER:
   87             return False
   88         elif owMode == OW_ALWAYS:
   89             return True
   90         elif owMode == OW_PROMPT:
   91             return yesOrNo(input(f"Overwrite {name} [y/N]: "))
   92         else:
   93             s = os.lstat(name)
   94             if s.st_mtime < info['mtime']:
   95                 # Current version is older
   96                 return True if owMode == OW_NEWER else False
   97             else:
   98                 # Current version is newer
   99                 return True if owMode == OW_OLDER else False
  100     else:
  101         return True
  102 
  103 def doAuthenticate(outname, checksum, digest):
  104     """
  105     Check that the recorded checksum of the file, and the digest of the generated file match.
  106     Perform the expected action if they don't.  Return the name of the file that's being generated.
  107     """
  108     logger.debug("File: %s Expected Hash: %s Hash: %s", outname, checksum, digest)
  109     # should use hmac.compare_digest() here, but it's not working for some reason.  Probably different types
  110     if not hmac.compare_digest(checksum, digest):
  111         if outname:
  112             if args.authfailaction == 'keep':
  113                 action = ''
  114                 target = outname
  115             elif args.authfailaction == 'rename':
  116                 target = outname + '-CORRUPT-' + str(digest)
  117                 action = 'Renaming to ' + target + '.'
  118                 try:
  119                     os.rename(outname, target)
  120                 except:
  121                     action = "Unable to rename to " + target + ".  File saved as " + outname + "."
  122             elif args.authfailaction == 'delete':
  123                 action = 'Deleting.'
  124                 os.unlink(outname)
  125                 target = None
  126         else:
  127             target = None
  128             action = ''
  129         if outname is None:
  130             outname = ''
  131         logger.error("File %s did not authenticate.  Expected: %s.  Got: %s.  %s",
  132                         outname, checksum, digest, action)
  133         return target
  134     else:
  135         return outname
  136 
  137 def notSame(a, b, string):
  138     if a == b:
  139         return ''
  140     else:
  141         return string
  142 
  143 def setAttributes(regenerator, info, outname):
  144     if outname:
  145         if args.setperm:
  146             try:
  147                 logger.debug("Setting permissions on %s to %o", outname, info['mode'])
  148                 os.chmod(outname, info['mode'])
  149             except Exception as e:
  150                 logger.warning("Unable to set permissions for %s", outname)
  151             try:
  152                 # Change the group, then the owner.
  153                 # Change the group first, as only root can change owner, and that might fail.
  154                 os.chown(outname, -1, info['gid'])
  155                 os.chown(outname, info['uid'], -1)
  156             except Exception as e:
  157                 logger.warning("Unable to set owner and group of %s", outname)
  158         if args.settime:
  159             try:
  160                 logger.debug("Setting times on %s to %d %d", outname, info['atime'], info['mtime'])
  161                 os.utime(outname, (info['atime'], info['mtime']))
  162             except Exception as e:
  163                 logger.warning("Unable to set times on %s", outname)
  164 
  165         if args.setattrs and 'attr' in info and info['attr']:
  166             try:
  167                 f = regenerator.recoverChecksum(info['attr'], authenticate)
  168                 xattrs = json.loads(f.read())
  169                 x = xattr.xattr(outname)
  170                 for attr in xattrs.keys():
  171                     value = base64.b64decode(xattrs[attr])
  172                     try:
  173                         x.set(attr, value)
  174                     except IOError:
  175                         logger.warning("Unable to set extended attribute %s on %s", attr, outname)
  176             except Exception as e:
  177                 logger.warning("Unable to process extended attributes for %s", outname)
  178         if args.setacl and 'acl' in info and info['acl']:
  179             try:
  180                 f = regenerator.recoverChecksum(info['acl'], authenticate)
  181                 acl = json.loads(f.read())
  182                 a = posix1e.ACL(text=acl)
  183                 a.applyto(outname)
  184             except Exception as e:
  185                 logger.warning("Unable to process extended attributes for %s", outname)
  186 
  187 def doRecovery(regenerator, info, authenticate, path, outname):
  188     myname = outname if outname else "stdout"
  189     logger.info("Recovering file %s %s", Util.shortPath(path), notSame(path, myname, " => " + Util.shortPath(myname)))
  190 
  191     checksum = info['checksum']
  192     i = regenerator.recoverChecksum(checksum, authenticate)
  193 
  194     if i:
  195         if authenticate:
  196             hasher = crypt.getHash()
  197 
  198         if info['link']:
  199             # read and make a link
  200             i.seek(0)
  201             x = i.read(16 * 1024)
  202             if outname:
  203                 os.symlink(x, outname)
  204             else:
  205                 logger.warning("No name specified for link: %s", x)
  206             if hasher:
  207                 hasher.update(x)
  208         else:
  209             if outname:
  210                 # Generate an output name
  211                 logger.debug("Writing output to %s", outname)
  212                 output = open(outname,  "wb")
  213             else:
  214                 output = sys.stdout.buffer
  215             try:
  216                 x = i.read(16 * 1024)
  217                 while x:
  218                     output.write(x)
  219                     if hasher:
  220                         hasher.update(x)
  221                     x = i.read(16 * 1024)
  222             except Exception as e:
  223                 logger.error("Unable to read file: {}: {}".format(i, repr(e)))
  224                 raise
  225             finally:
  226                 i.close()
  227                 if output is not sys.stdout.buffer:
  228                     output.close()
  229 
  230             if authenticate:
  231                 outname = doAuthenticate(outname, checksum, hasher.hexdigest())
  232 
  233             setAttributes(regenerator, info, outname)
  234 
  235 def recoverObject(regenerator, info, bset, outputdir, path, linkDB, name=None, authenticate=True):
  236     """
  237     Main recovery routine.  Recover an object, based on the info object, and put it in outputdir.
  238     """
  239     retCode = 0
  240     outname = None
  241     skip = False
  242     hasher = None
  243     try:
  244         if info:
  245             realname = crypt.decryptFilename(info['name'])
  246 
  247             if name:
  248                 # This should only happen only one file specified.
  249                 outname = name
  250             elif outputdir:
  251                 outname = os.path.abspath(os.path.join(outputdir, realname))
  252 
  253             if outname and not checkOverwrite(outname, info):
  254                 skip = True
  255                 try:
  256                     logger.warning("Skipping existing file: %s %s", Util.shortPath(path), notSame(path, outname, '(' + Util.shortPath(outname) + ')'))
  257                 except Exception:
  258                     pass
  259 
  260             # First, determine if we're in a linking situation
  261             if linkDB is not None and info['nlinks'] > 1 and not info['dir']:
  262                 key = (info['inode'], info['device'])
  263                 if key in linkDB:
  264                     logger.info("Linking %s to %s", outname, linkDB[key])
  265                     os.link(linkDB[key], outname)
  266                     skip = True
  267                 else:
  268                     linkDB[key] = outname
  269 
  270             # If it's a directory, create the directory, and recursively process it
  271             if info['dir']:
  272                 if not outname:
  273                     #logger.error("Cannot regenerate directory %s without outputdir specified", path)
  274                     raise Exception("Cannot regenerate directory %s without outputdir specified" % (path))
  275 
  276                 try:
  277                     logger.info("Processing directory %s", Util.shortPath(path))
  278                 except Exception:
  279                     pass
  280 
  281                 contents = list(tardis.readDirectory((info['inode'], info['device']), bset))
  282 
  283                 # Make sure an output directory is specified (really only useful at the top level)
  284                 if not os.path.exists(outname):
  285                     os.mkdir(outname)
  286 
  287                 setAttributes(regenerator, info, outname)
  288 
  289                 dirInode = (info['inode'], info['device'])
  290                 # For each file in the directory, regenerate it.
  291                 for i in contents:
  292                     name = crypt.decryptFilename(i['name'])
  293                     logger.debug("Processing file %s", name)
  294                     # Get the Info
  295                     childInfo = tardis.getFileInfoByName(i['name'], dirInode, bset)
  296                     logger.debug("Info on %s: %s", name, childInfo)
  297 
  298                     # Recurse into the child, if it exists.
  299                     if childInfo:
  300                         try:
  301                             if args.recurse or not childInfo['dir']:
  302                                 recoverObject(regenerator, childInfo, bset, outname, os.path.join(path, name), linkDB, authenticate=authenticate)
  303                         except Exception as e:
  304                             logger.error("Could not recover %s in %s", name, path)
  305                             if args.exceptions:
  306                                 logger.exception(e)
  307                     else:
  308                         logger.warning("No info on %s", name)
  309                         retCode += 1
  310             elif not skip:
  311                 doRecovery(regenerator, info, authenticate, path, outname)
  312 
  313     except Exception as e:
  314         logger.error("Recovery of %s failed. %s", outname, e)
  315         if args.exceptions:
  316             logger.exception(e)
  317         retCode += 1
  318 
  319     return retCode
  320 
  321 def setupPermissionChecks():
  322     uid = os.getuid()
  323     groups = os.getgroups()
  324 
  325     if uid == 0:
  326         return None     # If super-user, return None.  Causes no checking to happen.
  327 
  328     # Otherwise, create a closure function which can be used to do checking for each file.
  329     def checkPermission(pUid, pGid, mode):
  330         if stat.S_ISDIR(mode):
  331             if (uid == pUid) and (stat.S_IRUSR & mode) and (stat.S_IXUSR & mode):
  332                 return True
  333             elif (pGid in groups) and (stat.S_IRGRP & mode) and (stat.S_IXGRP & mode):
  334                 return True
  335             elif (stat.S_IROTH & mode) and (stat.S_IXOTH & mode):
  336                 return True
  337         else:
  338             if (uid == pUid) and (stat.S_IRUSR & mode):
  339                 return True
  340             elif (pGid in groups) and (stat.S_IRGRP & mode):
  341                 return True
  342             elif stat.S_IROTH & mode:
  343                 return True
  344         return False
  345 
  346     # And return the function.
  347     return checkPermission
  348 
  349 def findLastPath(path, reduce):
  350     logger.debug("findLastPath: %s", path)
  351     # Search all the sets in backwards order
  352     bsets = list(tardis.listBackupSets())
  353     for bset in reversed(bsets):
  354         logger.debug("Checking for path %s in %s (%d)", path, bset['name'], bset['backupset'])
  355         tmp = Util.reducePath(tardis, bset['backupset'], os.path.abspath(path), reduce, crypt)
  356         tmp2 = crypt.encryptPath(tmp)
  357         info = tardis.getFileInfoByPath(tmp2, bset['backupset'])
  358         if info:
  359             logger.debug("Found %s in backupset %s: %s", path, bset['name'], tmp)
  360             return bset['backupset'], tmp, bset['name']
  361     return (None, None, None)
  362 
  363 def recoverName(cksum):
  364     names = tardis.getNamesForChecksum(cksum)
  365     #print names
  366     if names:
  367         names = map(crypt.decryptFilename, names)
  368         name = names[0]
  369         if len(names) > 1:
  370             logger.warning("Multiple (%d) names for checksum %s %s.  Choosing '%s'.", len(names), cksum, map(str, list(names)), name)
  371         return name
  372     else:
  373         logger.error("No name discovered for checksum %s", cksum)
  374         return cksum
  375 
  376 def mkOutputDir(name):
  377     if os.path.isdir(name):
  378         return name
  379     elif os.path.exists(name):
  380         self.logger.error("%s is not a directory")
  381     else:
  382         os.mkdir(name)
  383         return name
  384 
  385 def parseArgs():
  386     parser = argparse.ArgumentParser(description='Recover Backed Up Files', fromfile_prefix_chars='@', formatter_class=Util.HelpFormatter, add_help=False)
  387 
  388     (_, remaining) = Config.parseConfigOptions(parser)
  389     Config.addCommonOptions(parser)
  390     Config.addPasswordOptions(parser)
  391 
  392     parser.add_argument("--output", "-o",   dest="output", help="Output file", default=None)
  393     parser.add_argument("--checksum", "-c", help="Use checksum instead of filename", dest='cksum', action='store_true', default=False)
  394 
  395     bsetgroup = parser.add_mutually_exclusive_group()
  396     bsetgroup.add_argument("--backup", "-b", help="Backup set to use.  Default: %(default)s", dest='backup', default=Defaults.getDefault('TARDIS_RECENT_SET'))
  397     bsetgroup.add_argument("--date", "-d",   help="Regenerate as of date", dest='date', default=None)
  398     bsetgroup.add_argument("--last", "-l",   dest='last', default=False, action='store_true', help="Regenerate the most recent version of the file")
  399 
  400     parser.add_argument('--recurse',        dest='recurse', default=True, action=Util.StoreBoolean, help='Recurse directory trees.  Default: %(default)s')
  401     parser.add_argument('--recovername',    dest='recovername', default=False, action=Util.StoreBoolean,    help='Recover the name when recovering a checksum.  Default: %(default)s')
  402 
  403     parser.add_argument('--authenticate',    dest='auth', default=True, action=Util.StoreBoolean,    help='Authenticate files while regenerating them.  Default: %(default)s')
  404     parser.add_argument('--authfail-action', dest='authfailaction', default='rename', choices=['keep', 'rename', 'delete'], help='Action to take for files that do not authenticate.  Default: %(default)s')
  405 
  406     parser.add_argument('--reduce-path', '-R',  dest='reduce',  default=0, const=sys.maxsize, type=int, nargs='?',   metavar='N',
  407                         help='Reduce path by N directories.  No value for "smart" reduction')
  408     parser.add_argument('--set-times', dest='settime', default=True, action=Util.StoreBoolean,      help='Set file times to match original file. Default: %(default)s')
  409     parser.add_argument('--set-perms', dest='setperm', default=True, action=Util.StoreBoolean,      help='Set file owner and permisions to match original file. Default: %(default)s')
  410     parser.add_argument('--set-attrs', dest='setattrs', default=True, action=Util.StoreBoolean,     help='Set file extended attributes to match original file.  May only set attributes in user space. Default: %(default)s')
  411     parser.add_argument('--set-acl',   dest='setacl', default=True, action=Util.StoreBoolean,       help='Set file access control lists to match the original file. Default: %(default)s')
  412     parser.add_argument('--overwrite', '-O', dest='overwrite', default=owModeDefault, const='always', nargs='?',
  413                         choices=['always', 'newer', 'older', 'never', 'ask'],
  414                         help='Mode for handling existing files. Default: %(default)s')
  415 
  416     parser.add_argument('--hardlinks',  dest='hardlinks',   default=True,   action=Util.StoreBoolean,   help='Create hardlinks of multiple copies of same inode created. Default: %(default)s')
  417 
  418     parser.add_argument('--exceptions',         default=False, action=Util.StoreBoolean, dest='exceptions', help="Log full exception data");
  419     parser.add_argument('--verbose', '-v',      action='count', default=0, dest='verbose', help='Increase the verbosity')
  420     parser.add_argument('--version',            action='version', version='%(prog)s ' + Tardis.__versionstring__,    help='Show the version')
  421     parser.add_argument('--help', '-h',         action='help')
  422 
  423     parser.add_argument('files', nargs='+', default=None, help="List of files to regenerate")
  424 
  425     Util.addGenCompletions(parser)
  426 
  427     return parser.parse_args(remaining)
  428 
  429 def main():
  430     global logger, crypt, tardis, args, owMode
  431     args = parseArgs()
  432     logger = Util.setupLogging(args.verbose, stream=sys.stderr)
  433 
  434     try:
  435         password = Util.getPassword(args.password, args.passwordfile, args.passwordprog, prompt="Password for %s: " % (args.client))
  436         args.password = None
  437         (tardis, cache, crypt) = Util.setupDataConnection(args.database, args.client, password, args.keys, args.dbname, args.dbdir)
  438 
  439         r = Regenerator.Regenerator(cache, tardis, crypt=crypt)
  440     except TardisDB.AuthenticationException as e:
  441         logger.error("Authentication failed.  Bad password")
  442         #if args.exceptions:
  443             #logger.exception(e)
  444         sys.exit(1)
  445     except Exception as e:
  446         logger.error("Regeneration failed: %s", e)
  447         if args.exceptions:
  448             logger.exception(e)
  449         sys.exit(1)
  450 
  451     try:
  452         bset = False
  453 
  454         if args.date:
  455             cal = parsedatetime.Calendar()
  456             (then, success) = cal.parse(args.date)
  457             if success:
  458                 timestamp = time.mktime(then)
  459                 logger.info("Using time: %s", time.asctime(then))
  460                 bsetInfo = tardis.getBackupSetInfoForTime(timestamp)
  461                 if bsetInfo and bsetInfo['backupset'] != 1:
  462                     bset = bsetInfo['backupset']
  463                     logger.debug("Using backupset: %s %d", bsetInfo['name'], bsetInfo['backupset'])
  464                 else:
  465                     logger.critical("No backupset at date: %s (%s)", args.date, time.asctime(then))
  466                     sys.exit(1)
  467             else:
  468                 logger.critical("Could not parse date string: %s", args.date)
  469                 sys.exit(1)
  470         elif args.backup:
  471             #bsetInfo = tardis.getBackupSetInfo(args.backup)
  472             bsetInfo = Util.getBackupSet(tardis, args.backup)
  473             if bsetInfo:
  474                 bset = bsetInfo['backupset']
  475             else:
  476                 logger.critical("No backupset at for name: %s", args.backup)
  477                 sys.exit(1)
  478 
  479         outputdir = None
  480         output    = sys.stdout.buffer
  481         outname   = None
  482         linkDB    = None
  483 
  484         owMode    = overwriteNames[args.overwrite]
  485 
  486         if args.output:
  487             if len(args.files) > 1:
  488                 outputdir = mkOutputDir(args.output)
  489             elif os.path.isdir(args.output):
  490                 outputdir = args.output
  491             else:
  492                 outname = args.output
  493         logger.debug("Outputdir: %s  Outname: %s", outputdir, outname)
  494 
  495         if args.hardlinks:
  496             linkDB = {}
  497 
  498         #if args.cksum and (args.settime or args.setperm):
  499             #logger.warning("Unable to set time or permissions on files specified by checksum.")
  500 
  501         permChecker = setupPermissionChecks()
  502 
  503         retcode = 0
  504         hasher = None
  505 
  506         # do the work here
  507         if args.cksum:
  508             for i in args.files:
  509                 try:
  510                     if args.auth:
  511                         hasher = crypt.getHash()
  512                     ckname = i
  513                     if args.recovername:
  514                         ckname = recoverName(i)
  515                     f = r.recoverChecksum(i, args.auth)
  516                     if f:
  517                         logger.info("Recovering checksum %s", ckname)
  518                     # Generate an output name
  519                         if outname:
  520                             # Note, this should ONLY be true if only one file
  521                             output = open(outname,  "wb")
  522                         elif outputdir:
  523                             outname = os.path.join(outputdir, ckname)
  524                             if os.path.exists(outname) and owMode == OW_NEVER:
  525                                 logger.warning("File %s exists.  Skipping", outname)
  526                                 continue
  527                             logger.debug("Writing output to %s", outname)
  528                             output = open(outname,  "wb")
  529                         elif outname:
  530                             # Note, this should ONLY be true if only one file
  531                             if os.path.exists(outname) and owMode == OW_NEVER:
  532                                 logger.warning("File %s exists.  Skipping", outname)
  533                                 continue
  534                             output = file(outname,  "wb")
  535                         try:
  536                             x = f.read(64 * 1024)
  537                             while x:
  538                                 output.write(x)
  539                                 if hasher:
  540                                     hasher.update(x)
  541                                 x = f.read(64 * 1024)
  542                         except Exception as e:
  543                             logger.error("Unable to read file: {}: {}".format(i, repr(e)))
  544                             raise
  545                         finally:
  546                             f.close()
  547                             if output is not sys.stdout.buffer:
  548                                 output.close()
  549                         if args.auth:
  550                             logger.debug("Checking authentication")
  551                             outname = doAuthenticate(outname, i, hasher.hexdigest())
  552 
  553                 except TardisDB.AuthenticationException as e:
  554                     logger.error("Authentication failed.  Bad password")
  555                     #if args.exceptions:
  556                         #logger.exception(e)
  557                     sys.exit(1)
  558                 except Exception as e:
  559                     logger.error("Could not recover: %s: %s", i, e)
  560                     if args.exceptions:
  561                         logger.exception(e)
  562                     retcode += 1
  563 
  564         else: # Not checksum, but acutal pathnames
  565             for i in args.files:
  566                 try:
  567                     i = os.path.abspath(i)
  568                     logger.info("Processing %s", Util.shortPath(i))
  569                     path = None
  570                     f = None
  571                     if args.last:
  572                         (bset, path, name) = findLastPath(i, args.reduce)
  573                         if bset is None:
  574                             logger.error("Unable to find a latest version of %s", i)
  575                             raise Exception("Unable to find a latest version of " + i)
  576                         logger.info("Found %s in backup set %s", i, name)
  577                     elif args.reduce:
  578                         path = Util.reducePath(tardis, bset, i, args.reduce, crypt)
  579                         logger.debug("Reduced path %s to %s", path, i)
  580                         if not path:
  581                             logger.error("Unable to find a compute path for %s", i)
  582                             raise Exception("Unable to compute path for " + i)
  583                     else:
  584                         path = i
  585 
  586                     actualPath = crypt.encryptPath(path)
  587 
  588                     logger.debug("Actual path is %s -- %s", actualPath, bset)
  589                     info = tardis.getFileInfoByPath(actualPath, bset)
  590                     if info:
  591                         retcode += recoverObject(r, info, bset, outputdir, path, linkDB, name=outname, authenticate=args.auth)
  592                     else:
  593                         logger.error("Could not recover info for %s (File not found)", i)
  594                         retcode += 1
  595                 except TardisDB.AuthenticationException as e:
  596                     logger.error("Authentication failed.  Bad password")
  597                     #if args.exceptions:
  598                         #logger.exception(e)
  599                     sys.exit(1)
  600                 except Exception as e:
  601                     logger.error("Could not recover: %s: %s", i, e)
  602                     if args.exceptions:
  603                         logger.exception(e)
  604     except KeyboardInterrupt:
  605         logger.error("Recovery interupted")
  606     except TardisDB.AuthenticationException as e:
  607         logger.error("Authentication failed.  Bad password")
  608         if args.exceptions:
  609             logger.exception(e)
  610     except Exception as e:
  611         logger.error("Regeneration failed: %s", e)
  612         if args.exceptions:
  613             logger.exception(e)
  614 
  615     if errors:
  616         logger.warning("%d files could not be recovered.")
  617 
  618     return retcode
  619 
  620 if __name__ == "__main__":
  621     sys.exit(main())
  622