def main(): global logger, crypt, tardis, args, owMode args = parseArgs() logger = Util.setupLogging(args.verbose, stream=sys.stderr) try: password = Util.getPassword(args.password, args.passwordfile, args.passwordprog, prompt="Password for %s: " % (args.client)) args.password = None (tardis, cache, crypt) = Util.setupDataConnection(args.database, args.client, password, args.keys, args.dbname, args.dbdir) r = Regenerator.Regenerator(cache, tardis, crypt=crypt) except TardisDB.AuthenticationException as e: logger.error("Authentication failed. Bad password") #if args.exceptions: #logger.exception(e) sys.exit(1) except Exception as e: logger.error("Regeneration failed: %s", e) sys.exit(1) try: bset = False if args.date: cal = parsedatetime.Calendar() (then, success) = cal.parse(args.date) if success: timestamp = time.mktime(then) logger.info("Using time: %s", time.asctime(then)) bsetInfo = tardis.getBackupSetInfoForTime(timestamp) if bsetInfo and bsetInfo['backupset'] != 1: bset = bsetInfo['backupset'] logger.debug("Using backupset: %s %d", bsetInfo['name'], bsetInfo['backupset']) else: logger.critical("No backupset at date: %s (%s)", args.date, time.asctime(then)) sys.exit(1) else: logger.critical("Could not parse date string: %s", args.date) sys.exit(1) elif args.backup: #bsetInfo = tardis.getBackupSetInfo(args.backup) bsetInfo = Util.getBackupSet(tardis, args.backup) if bsetInfo: bset = bsetInfo['backupset'] else: logger.critical("No backupset at for name: %s", args.backup) sys.exit(1) outputdir = None output = sys.stdout.buffer outname = None linkDB = None owMode = overwriteNames[args.overwrite] if args.output: if len(args.files) > 1: outputdir = mkOutputDir(args.output) elif os.path.isdir(args.output): outputdir = args.output else: outname = args.output logger.debug("Outputdir: %s Outname: %s", outputdir, outname) if args.hardlinks: linkDB = {} #if args.cksum and (args.settime or args.setperm): #logger.warning("Unable to set time or permissions on files specified by checksum.") permChecker = setupPermissionChecks() retcode = 0 hasher = None # do the work here if args.cksum: for i in args.files: try: if args.auth: hasher = Util.getHash(crypt) ckname = i if args.recovername: ckname = recoverName(i) f = r.recoverChecksum(i, args.auth) if f: logger.info("Recovering checksum %s", ckname) # Generate an output name if outname: # Note, this should ONLY be true if only one file output = open(outname, "wb") elif outputdir: outname = os.path.join(outputdir, ckname) if os.path.exists(outname) and owMode == OW_NEVER: logger.warning("File %s exists. Skipping", outname) continue logger.debug("Writing output to %s", outname) output = open(outname, "wb") elif outname: # Note, this should ONLY be true if only one file if os.path.exists(outname) and owMode == OW_NEVER: logger.warning("File %s exists. Skipping", outname) continue output = file(outname, "wb") try: x = f.read(64 * 1024) while x: output.write(x) if hasher: hasher.update(x) x = f.read(64 * 1024) except Exception as e: logger.error("Unable to read file: {}: {}".format(i, repr(e))) raise finally: f.close() if output is not sys.stdout.buffer: output.close() if args.auth: logger.debug("Checking authentication") outname = doAuthenticate(outname, i, hasher.hexdigest()) except TardisDB.AuthenticationException as e: logger.error("Authentication failed. Bad password") #if args.exceptions: #logger.exception(e) sys.exit(1) except Exception as e: logger.error("Could not recover: %s: %s", i, e) if args.exceptions: logger.exception(e) retcode += 1 else: # Not checksum, but acutal pathnames for i in args.files: try: i = os.path.abspath(i) logger.info("Processing %s", Util.shortPath(i)) path = None f = None if args.last: (bset, path, name) = findLastPath(i, args.reduce) if bset is None: logger.error("Unable to find a latest version of %s", i) raise Exception("Unable to find a latest version of " + i) logger.info("Found %s in backup set %s", i, name) elif args.reduce: path = Util.reducePath(tardis, bset, i, args.reduce, crypt) logger.debug("Reduced path %s to %s", path, i) if not path: logger.error("Unable to find a compute path for %s", i) raise Exception("Unable to compute path for " + i) else: path = i if args.crypt and crypt: actualPath = crypt.encryptPath(path) else: actualPath = path logger.debug("Actual path is %s -- %s", actualPath, bset) info = tardis.getFileInfoByPath(actualPath, bset) if info: retcode += recoverObject(r, info, bset, outputdir, path, linkDB, name=outname, authenticate=args.auth) else: logger.error("Could not recover info for %s (File not found)", i) retcode += 1 except TardisDB.AuthenticationException as e: logger.error("Authentication failed. Bad password") #if args.exceptions: #logger.exception(e) sys.exit(1) except Exception as e: logger.error("Could not recover: %s: %s", i, e) if args.exceptions: logger.exception(e) except KeyboardInterrupt: logger.error("Recovery interupted") except TardisDB.AuthenticationException as e: logger.error("Authentication failed. Bad password") if args.exceptions: logger.exception(e) except Exception as e: logger.error("Regeneration failed: %s", e) if args.exceptions: logger.exception(e) if errors: logger.warning("%d files could not be recovered.") return retcode
def recoverObject(regenerator, info, bset, outputdir, path, linkDB, name=None, authenticate=True): """ Main recovery routine. Recover an object, based on the info object, and put it in outputdir. """ retCode = 0 outname = None skip = False hasher = None try: if info: realname = info['name'] if args.crypt and crypt: realname = crypt.decryptFilename(realname) if name: # This should only happen only one file specified. outname = name elif outputdir: outname = os.path.abspath(os.path.join(outputdir, realname)) if outname and not checkOverwrite(outname, info): skip = True try: logger.warning("Skipping existing file: %s %s", Util.shortPath(path), notSame(path, outname, '(' + Util.shortPath(outname) + ')')) except Exception: pass # First, determine if we're in a linking situation if linkDB is not None and info['nlinks'] > 1 and not info['dir']: key = (info['inode'], info['device']) if key in linkDB: logger.info("Linking %s to %s", outname, linkDB[key]) os.link(linkDB[key], outname) skip = True else: linkDB[key] = outname # If it's a directory, create the directory, and recursively process it if info['dir']: if not outname: #logger.error("Cannot regenerate directory %s without outputdir specified", path) raise Exception("Cannot regenerate directory %s without outputdir specified" % (path)) try: logger.info("Processing directory %s", Util.shortPath(path)) except Exception: pass contents = list(tardis.readDirectory((info['inode'], info['device']), bset)) # Make sure an output directory is specified (really only useful at the top level) if not os.path.exists(outname): os.mkdir(outname) dirInode = (info['inode'], info['device']) # For each file in the directory, regenerate it. for i in contents: name = i['name'] # Get the Info childInfo = tardis.getFileInfoByName(name, dirInode, bset) # Decrypt filename, and make it UTF-8. if args.crypt and crypt: name = crypt.decryptFilename(name) else: name = name # Recurse into the child, if it exists. if childInfo: try: if args.recurse or not childInfo['dir']: recoverObject(regenerator, childInfo, bset, outname, os.path.join(path, name), linkDB, authenticate=authenticate) except Exception as e: logger.error("Could not recover %s in %s", name, path) if args.exceptions: logger.exception(e) else: retCode += 1 elif not skip: myname = outname if outname else "stdout" logger.info("Recovering file %s %s", Util.shortPath(path), notSame(path, myname, " => " + Util.shortPath(myname))) checksum = info['checksum'] i = regenerator.recoverChecksum(checksum, authenticate) if i: if authenticate: hasher = Util.getHash(crypt) if info['link']: # read and make a link i.seek(0) x = i.read(16 * 1024) if outname: os.symlink(x, outname) else: logger.warning("No name specified for link: %s", x) if hasher: hasher.update(x) else: if outname: # Generate an output name logger.debug("Writing output to %s", outname) output = open(outname, "wb") else: output = sys.stdout.buffer try: x = i.read(16 * 1024) while x: output.write(x) if hasher: hasher.update(x) x = i.read(16 * 1024) except Exception as e: logger.error("Unable to read file: {}: {}".format(i, repr(e))) raise finally: i.close() if output is not sys.stdout.buffer: output.close() if authenticate: outname = doAuthenticate(outname, checksum, hasher.hexdigest()) if outname and not skip: if args.setperm: try: logger.debug("Setting permissions on %s to %o", outname, info['mode']) os.chmod(outname, info['mode']) except Exception as e: logger.warning("Unable to set permissions for %s", outname) try: # Change the group, then the owner. # Change the group first, as only root can change owner, and that might fail. os.chown(outname, -1, info['gid']) os.chown(outname, info['uid'], -1) except Exception as e: logger.warning("Unable to set owner and group of %s", outname) if args.settime: try: logger.debug("Setting times on %s to %d %d", outname, info['atime'], info['mtime']) os.utime(outname, (info['atime'], info['mtime'])) except Exception as e: logger.warning("Unable to set times on %s", outname) if args.setattrs and 'attr' in info and info['attr']: try: f = regenerator.recoverChecksum(info['attr'], authenticate) xattrs = json.loads(f.read()) x = xattr.xattr(outname) for attr in xattrs.keys(): value = base64.b64decode(xattrs[attr]) try: x.set(attr, value) except IOError: logger.warning("Unable to set extended attribute %s on %s", attr, outname) except Exception as e: logger.warning("Unable to process extended attributes for %s", outname) if args.setacl and 'acl' in info and info['acl']: try: f = regenerator.recoverChecksum(info['acl'], authenticate) acl = json.loads(f.read()) a = posix1e.ACL(text=acl) a.applyto(outname) except Exception as e: logger.warning("Unable to process extended attributes for %s", outname) except Exception as e: logger.error("Recovery of %s failed. %s", outname, e) if args.exceptions: logger.exception(e) retCode += 1 return retCode