]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/helpers.py
Merge branch 'master' into config
[bup.git] / lib / bup / helpers.py
index 62d5b26c32d1a7dbf82a9007ecc51c489a8b748b..d9d177cabf30931a14e074e828204c2b7ed13999 100644 (file)
@@ -1,8 +1,9 @@
 """Helper functions and classes for bup."""
 
 import sys, os, pwd, subprocess, errno, socket, select, mmap, stat, re, struct
-import heapq, operator
-from bup import _version
+import heapq, operator, time, platform
+from bup import _version, _helpers
+import bup._helpers as _helpers
 
 # This function should really be in helpers, not in bup.options.  But we
 # want options.py to be standalone so people can include it in other projects.
@@ -46,10 +47,14 @@ def _hard_write(fd, buf):
         assert(sz >= 0)
         buf = buf[sz:]
 
+
+_last_prog = 0
 def log(s):
     """Print a log message to stderr."""
+    global _last_prog
     sys.stdout.flush()
     _hard_write(sys.stderr.fileno(), s)
+    _last_prog = 0
 
 
 def debug1(s):
@@ -62,6 +67,39 @@ def debug2(s):
         log(s)
 
 
+istty1 = os.isatty(1) or (atoi(os.environ.get('BUP_FORCE_TTY')) & 1)
+istty2 = os.isatty(2) or (atoi(os.environ.get('BUP_FORCE_TTY')) & 2)
+_last_progress = ''
+def progress(s):
+    """Calls log() if stderr is a TTY.  Does nothing otherwise."""
+    global _last_progress
+    if istty2:
+        log(s)
+        _last_progress = s
+
+
+def qprogress(s):
+    """Calls progress() only if we haven't printed progress in a while.
+    
+    This avoids overloading the stderr buffer with excess junk.
+    """
+    global _last_prog
+    now = time.time()
+    if now - _last_prog > 0.1:
+        progress(s)
+        _last_prog = now
+
+
+def reprogress():
+    """Calls progress() to redisplay the most recent progress message.
+
+    Useful after you've printed some other message that wipes out the
+    progress line.
+    """
+    if _last_progress and _last_progress.endswith('\r'):
+        progress(_last_progress)
+
+
 def mkdirp(d, mode=None):
     """Recursively create directories on path 'd'.
 
@@ -160,6 +198,19 @@ def realpath(p):
     return out
 
 
+def detect_fakeroot():
+    "Return True if we appear to be running under fakeroot."
+    return os.getenv("FAKEROOTKEY") != None
+
+
+def is_superuser():
+    if platform.system().startswith('CYGWIN'):
+        import ctypes
+        return ctypes.cdll.shell32.IsUserAnAdmin()
+    else:
+        return os.geteuid() == 0
+
+
 _username = None
 def username():
     """Get the user's login name."""
@@ -202,9 +253,11 @@ def resource_path(subdir=''):
         _resource_path = os.environ.get('BUP_RESOURCE_PATH') or '.'
     return os.path.join(_resource_path, subdir)
 
+
 class NotOk(Exception):
     pass
 
+
 class BaseConn:
     def __init__(self, outp):
         self.outp = outp
@@ -268,6 +321,7 @@ class BaseConn:
             raise Exception('expected "ok", got %r' % rl)
         return self._check_ok(onempty)
 
+
 class Conn(BaseConn):
     def __init__(self, inp, outp):
         BaseConn.__init__(self, outp)
@@ -287,6 +341,7 @@ class Conn(BaseConn):
         else:
             return None
 
+
 def checked_reader(fd, n):
     while n > 0:
         rl, _, _ = select.select([fd], [], [])
@@ -296,6 +351,7 @@ def checked_reader(fd, n):
         yield buf
         n -= len(buf)
 
+
 MAX_PACKET = 128 * 1024
 def mux(p, outfd, outr, errr):
     try:
@@ -314,6 +370,7 @@ def mux(p, outfd, outr, errr):
     finally:
         os.write(outfd, struct.pack('!IB', 0, 3))
 
+
 class DemuxConn(BaseConn):
     """A helper class for bup's client-server protocol."""
     def __init__(self, infd, outp):
@@ -322,10 +379,12 @@ class DemuxConn(BaseConn):
         # multiplexed and can be assumed to be debug/log before mux init.
         tail = ''
         while tail != 'BUPMUX':
-            tail += os.read(infd, 1024)
-            buf = tail[:-6]
+            b = os.read(infd, (len(tail) < 6) and (6-len(tail)) or 1)
+            if not b:
+                raise IOError('demux: unexpected EOF during initialization')
+            tail += b
+            sys.stderr.write(tail[:-6])  # pre-mux log messages
             tail = tail[-6:]
-            sys.stderr.write(buf)
         self.infd = infd
         self.reader = None
         self.buf = None
@@ -342,7 +401,7 @@ class DemuxConn(BaseConn):
         assert(rl[0] == self.infd)
         ns = ''.join(checked_reader(self.infd, 5))
         n, fdw = struct.unpack('!IB', ns)
-        assert(n<=MAX_PACKET)
+        assert(n <= MAX_PACKET)
         if fdw == 1:
             self.reader = checked_reader(self.infd, n)
         elif fdw == 2:
@@ -402,6 +461,7 @@ class DemuxConn(BaseConn):
     def has_input(self):
         return self._load_buf(0)
 
+
 def linereader(f):
     """Generate a list of input lines from 'f' without terminating newlines."""
     while 1:
@@ -441,7 +501,7 @@ def slashappend(s):
         return s
 
 
-def _mmap_do(f, sz, flags, prot):
+def _mmap_do(f, sz, flags, prot, close):
     if not sz:
         st = os.fstat(f.fileno())
         sz = st.st_size
@@ -451,24 +511,34 @@ def _mmap_do(f, sz, flags, prot):
         # no elements :)
         return ''
     map = mmap.mmap(f.fileno(), sz, flags, prot)
-    f.close()  # map will persist beyond file close
+    if close:
+        f.close()  # map will persist beyond file close
     return map
 
 
-def mmap_read(f, sz = 0):
+def mmap_read(f, sz = 0, close=True):
     """Create a read-only memory mapped region on file 'f'.
-
     If sz is 0, the region will cover the entire file.
     """
-    return _mmap_do(f, sz, mmap.MAP_PRIVATE, mmap.PROT_READ)
+    return _mmap_do(f, sz, mmap.MAP_PRIVATE, mmap.PROT_READ, close)
 
 
-def mmap_readwrite(f, sz = 0):
+def mmap_readwrite(f, sz = 0, close=True):
     """Create a read-write memory mapped region on file 'f'.
+    If sz is 0, the region will cover the entire file.
+    """
+    return _mmap_do(f, sz, mmap.MAP_SHARED, mmap.PROT_READ|mmap.PROT_WRITE,
+                    close)
 
+
+def mmap_readwrite_private(f, sz = 0, close=True):
+    """Create a read-write memory mapped region on file 'f'.
     If sz is 0, the region will cover the entire file.
+    The map is private, which means the changes are never flushed back to the
+    file.
     """
-    return _mmap_do(f, sz, mmap.MAP_SHARED, mmap.PROT_READ|mmap.PROT_WRITE)
+    return _mmap_do(f, sz, mmap.MAP_PRIVATE, mmap.PROT_READ|mmap.PROT_WRITE,
+                    close)
 
 
 def parse_num(s):
@@ -515,11 +585,10 @@ def add_error(e):
     saved_errors.append(e)
     log('%-70s\n' % e)
 
-istty = os.isatty(2) or atoi(os.environ.get('BUP_FORCE_TTY'))
-def progress(s):
-    """Calls log(s) if stderr is a TTY.  Does nothing otherwise."""
-    if istty:
-        log(s)
+
+def clear_errors():
+    global saved_errors
+    saved_errors = []
 
 
 def handle_ctrl_c():
@@ -562,6 +631,7 @@ def columnate(l, prefix):
         out += prefix + ''.join(('%-*s' % (clen+2, s)) for s in row) + '\n'
     return out
 
+
 def parse_date_or_fatal(str, fatal):
     """Parses the given date or calls Option.fatal().
     For now we expect a string that contains a float."""
@@ -572,6 +642,7 @@ def parse_date_or_fatal(str, fatal):
     else:
         return date
 
+
 def strip_path(prefix, path):
     """Strips a given prefix from a path.
 
@@ -591,6 +662,7 @@ def strip_path(prefix, path):
     else:
         return path
 
+
 def strip_base_path(path, base_paths):
     """Strips the base path from a given path.
 
@@ -607,6 +679,7 @@ def strip_base_path(path, base_paths):
             return strip_path(bp, normalized_path)
     return path
 
+
 def graft_path(graft_points, path):
     normalized_path = os.path.realpath(path)
     for graft_point in graft_points:
@@ -633,10 +706,12 @@ def version_date():
     """Format bup's version date string for output."""
     return _version.DATE.split(' ')[0]
 
+
 def version_commit():
     """Get the commit hash of bup's current version."""
     return _version.COMMIT
 
+
 def version_tag():
     """Format bup's version tag (the official version number).