Fix blocking bug of tar's output
authorStefan Huber <shuber2@gmx.at>
Tue, 15 May 2012 09:15:51 +0000 (11:15 +0200)
committerStefan Huber <shuber2@gmx.at>
Tue, 15 May 2012 09:15:51 +0000 (11:15 +0200)
shbackup

index 41658696b6097a9272b3e49a0f7e291ec515e004..d5b8b812073f75208576f3b80e2465d0d3757ff1 100755 (executable)
--- a/shbackup
+++ b/shbackup
@@ -8,7 +8,7 @@ import datetime
 import os, shutil, sys
 import configparser
 import hashlib
-import subprocess
+import subprocess, fcntl
 import random, re
 import logging
 
@@ -269,26 +269,46 @@ class BackupManager:
         for pat in self.conf.exclpatterns:
             taropts += ["--exclude", pat]
 
+        # Launch the tar process
         tarargs = [tarpath] + taropts + ["-f", fsfn] + fileset.dirs
         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)
+
+        def readlineNonBlocking(stream):
+            """Read a line nonblocking. Returns b'' if nothing read."""
+            try:
+                return stream.readline()
+            except:
+                return b''
+                pass
+
+
+        # Read stdout and stderr of tarp
+        errmsg = b""
         while tarp.poll() == None:
-            l = tarp.stdout.readline()
-            if l != "":
-                logging.debug(l.decode().rstrip())
+            l = readlineNonBlocking(tarp.stdout)
+            if l != b"":
+                logging.debug(l[:-1].decode())
+            errmsg += readlineNonBlocking(tarp.stderr)
 
-        # 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():
+            for l in errmsg.split("\n"):
                 logfile.error( l.decode().strip().rstrip() )
-            sys.stderr.write( tarp.stderr.read().decode() )
             logfile.error(tarpath + " returned with exit status " + str(rett) + ".")
 
 
@@ -415,7 +435,11 @@ 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 LogConf.con.level <= logging.INFO:
@@ -447,7 +471,7 @@ def printUsage():
     print("  -m, --mode <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")