X-Git-Url: https://git.sthu.org/?p=sitarba.git;a=blobdiff_plain;f=shbackup;h=0cfc4c74f3a03f5a0d2c846ef7c191f11600770f;hp=da28afecb1c8532307a7c795b29bf63be63ee2a1;hb=5efcea243bc2cade77760e5d5c5643ad0dd5ca11;hpb=bbe0d5015e6e07edc52d3735515cfa0602d18194 diff --git a/shbackup b/shbackup index da28afe..0cfc4c7 100755 --- a/shbackup +++ b/shbackup @@ -8,7 +8,7 @@ import datetime import os, shutil, sys import configparser import hashlib -import subprocess +import subprocess, fcntl, select import random, re import logging @@ -57,8 +57,14 @@ class Backup: ", mode: " + self.mode + "]" def colAlignedString(self): - return "%16s %8s %4s" % ( \ - self.date.strftime("%Y-%m-%d %H:%M"), self.epoch, self.mode) + age = datetime.datetime.now() - self.date + total_hours = age.total_seconds()/3600 + if total_hours <= 48: + agestr = "(%s h)" % int(total_hours) + else: + agestr = "(%s d)" % age.days + return "%16s %7s %8s %4s" % ( \ + self.date.strftime("%Y-%m-%d %H:%M"), agestr, self.epoch, self.mode) @staticmethod def getDirName(date, epoch, mode): @@ -255,41 +261,57 @@ class BackupManager: def backupFileSet(self, fileset, targetdir, since=None): """Create an archive for given fileset at given target directory.""" - logger = logging.getLogger('backup') + logfile = logging.getLogger('backuplog') + logfile.info("Running file set: " + fileset.name) - logger.info("Running file set: " + fileset.name) tarpath = "/bin/tar" fsfn = os.path.join(targetdir, fileset.name) + "." + self.conf.format - taropts = ["-cpva"] + taropts = [] + # Add the since date, if given if since != None: taropts += ["-N", since.strftime("%Y-%m-%d %H:%M:%S")] + # Add the exclude patterns for pat in self.conf.exclpatterns: taropts += ["--exclude", pat] - tarargs = [tarpath] + taropts + ["-f", fsfn] + fileset.dirs - logger.debug("tar call: " + " ".join(tarargs)) + # Adding directories to backup + taropts += ["-C", "/"] + [ "./" + d.lstrip("/") for d in fileset.dirs] + + # Launch the tar process + tarargs = [tarpath] + ["-cpvaf", fsfn] + taropts + logfile.debug("tar call: " + " ".join(tarargs)) tarp = subprocess.Popen( tarargs, bufsize=-1, \ stdout=subprocess.PIPE, stderr=subprocess.PIPE ) - # Output stdout of tar + # Change tarp's stdout and stderr to non-blocking + for s in [tarp.stdout, tarp.stderr]: + fd = s.fileno() + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) + + # Read stdout and stderr of tarp + errmsg = b"" while tarp.poll() == None: - l = tarp.stdout.readline() - if l != "": - logging.debug(l.decode().rstrip()) + rd,wr,ex = select.select([tarp.stdout, tarp.stderr], [], [], 0.05) + if tarp.stdout in rd: + logging.debug( tarp.stdout.readline()[:-1].decode() ) + if tarp.stderr in rd: + errmsg += tarp.stderr.read() - # Output remaining output of tar + # Get the remainging output of tarp for l in tarp.stdout.readlines(): logging.debug(l.decode().rstrip()) + errmsg += tarp.stderr.read() + # Get return code of tarp rett = tarp.wait() if rett != 0: - for l in tarp.stderr.readlines(): - logger.error( l.decode().strip().rstrip() ) - sys.stderr.write( tarp.stderr.read().decode() ) - logger.error(tarpath + " returned with exit status " + str(rett) + ".") + for l in errmsg.decode().split("\n"): + logfile.error(l) + logfile.error(tarpath + " returned with exit status " + str(rett) + ".") def backup(self, epoch=None, mode=None): @@ -345,17 +367,19 @@ class BackupManager: os.mkdir( targetdir ) - logger = logging.getLogger('backup') - ch = logging.FileHandler( os.path.join(targetdir, "log") ) - ch.setLevel(logging.INFO) - logger.addHandler(ch) - logger.info("Started: " + now.ctime()) + # Add file logger + logfile = logging.getLogger("backuplog") + fil = logging.FileHandler( os.path.join(targetdir, "log") ) + fil.setLevel(logging.DEBUG) + logfile.addHandler(fil) + + logfile.info("Started: " + now.ctime()) # Backup all file sets for s in self.conf.sets: self.backupFileSet(s, targetdir, since) - logger.info("Stopped: " + datetime.datetime.now().ctime()) + logfile.info("Stopped: " + datetime.datetime.now().ctime()) # Rename backup directory to final name os.rename( targetdir, os.path.join(basedir, dirname) ) @@ -413,10 +437,14 @@ class BackupManager: yesno = self.ask_user_yesno("Remove entries marked by '*'? [y, N] ") if yesno == "y": for d in removeDirs: - shutil.rmtree(os.path.join(basedir, d)) + try: + shutil.rmtree(os.path.join(basedir, d)) + except OSError as e: + logging.error("Error when removing '%s': %s" % (d,e.strerror) ) + def ask_user_yesno(self, question): - if logging.getLogger().isEnabledFor(logging.INFO): + if LogConf.con.level <= logging.INFO: return input(question) else: return "y" @@ -445,13 +473,33 @@ def printUsage(): print(" -m, --mode override mode: full, diff, or incr") print(" -v, --verbose be more verbose and interact with user") print(" --verbosity LEVEL set verbosity to LEVEL, which can be") - print(" warning, info, debug") + print(" error, warning, info, debug") print(" -V, --version print version info") + +class LogConf: + """Encapsulates logging configuration""" + + con = logging.StreamHandler(sys.stderr) + + @classmethod + def setup(cls): + """Setup logging system""" + conlog = logging.getLogger() + conlog.setLevel(logging.DEBUG) + + cls.con.setLevel(logging.WARNING) + conlog.addHandler(cls.con) + + fillog = logging.getLogger("backuplog") + fillog.setLevel(logging.DEBUG) + + if __name__ == "__main__": - logging.basicConfig(format='%(message)s') + LogConf.setup() + conffn = "/etc/shbackup.conf" cmd = "list" mode = None @@ -475,7 +523,7 @@ if __name__ == "__main__": exit(0) elif opt in ["-v", "--verbose"]: - logging.getLogger().setLevel(logging.INFO) + LogConf.con.setLevel(logging.INFO) elif opt in ["--verbosity"]: i += 1 @@ -483,7 +531,7 @@ if __name__ == "__main__": numlevel = getattr(logging, level.upper(), None) if not isinstance(numlevel, int): raise ValueError('Invalid verbosity level: %s' % level) - logging.getLogger().setLevel(numlevel) + LogConf.con.setLevel(numlevel) elif opt in ["-m", "--mode"]: i += 1