]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/helpers.py
Fix typo in atomically_replaced_file docstring
[bup.git] / lib / bup / helpers.py
index 1b390c51dd51ea3f11481f2e55531a86cf0a62c4..715ce3389dcc9550e9447fb86025c68c2d677707 100644 (file)
@@ -2,8 +2,9 @@
 
 from ctypes import sizeof, c_void_p
 from os import environ
+from contextlib import contextmanager
 import sys, os, pwd, subprocess, errno, socket, select, mmap, stat, re, struct
-import hashlib, heapq, operator, time, grp
+import hashlib, heapq, operator, time, grp, tempfile
 
 from bup import _helpers
 import bup._helpers as _helpers
@@ -187,8 +188,8 @@ def unlink(f):
     try:
         os.unlink(f)
     except OSError, e:
-        if e.errno == errno.ENOENT:
-            pass  # it doesn't exist, that's what you asked for
+        if e.errno != errno.ENOENT:
+            raise
 
 
 def readpipe(argv, preexec_fn=None):
@@ -628,6 +629,42 @@ def chunkyreader(f, count = None):
             yield b
 
 
+@contextmanager
+def atomically_replaced_file(name, mode='w', buffering=-1):
+    """Yield a file that will be atomically renamed name when leaving the block.
+
+    This contextmanager yields an open file object that is backed by a
+    temporary file which will be renamed (atomically) to the target
+    name if everything succeeds.
+
+    The mode and buffering arguments are handled exactly as with open,
+    and the yielded file will have very restrictive permissions, as
+    per mkstemp.
+
+    E.g.::
+
+        with atomically_replaced_file('foo.txt', 'w') as f:
+            f.write('hello jack.')
+
+    """
+
+    (ffd, tempname) = tempfile.mkstemp(dir=os.path.dirname(name),
+                                       text=('b' not in mode))
+    try:
+        try:
+            f = os.fdopen(ffd, mode, buffering)
+        except:
+            os.close(ffd)
+            raise
+        try:
+            yield f
+        finally:
+            f.close()
+        os.rename(tempname, name)
+    finally:
+        unlink(tempname)  # nonexistant file is ignored
+
+
 def slashappend(s):
     """Append "/" to 's' if it doesn't aleady end in "/"."""
     if s and not s.endswith('/'):
@@ -812,7 +849,10 @@ def parse_excludes(options, fatal):
             except IOError, e:
                 raise fatal("couldn't read %s" % parameter)
             for exclude_path in f.readlines():
-                excluded_paths.append(realpath(exclude_path.strip()))
+                # FIXME: perhaps this should be rstrip('\n')
+                exclude_path = realpath(exclude_path.strip())
+                if exclude_path:
+                    excluded_paths.append(exclude_path)
     return sorted(frozenset(excluded_paths))
 
 
@@ -835,6 +875,8 @@ def parse_rx_excludes(options, fatal):
                 raise fatal("couldn't read %s" % parameter)
             for pattern in f.readlines():
                 spattern = pattern.rstrip('\n')
+                if not spattern:
+                    continue
                 try:
                     excluded_patterns.append(re.compile(spattern))
                 except re.error, ex: