D6555: vfs: require use of .seek() or .write() before .tell() on append-mode files

durin42 (Augie Fackler) phabricator at mercurial-scm.org
Thu Jun 20 18:29:31 UTC 2019


durin42 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This prevents the bug in the previous change, but avoids an extraneous
  seek() call in the common case when it's not required. My preference
  was to ban .seek() and .tell() entirely on append-mode files since
  they're potentially misleading, but our codebase doesn't make that
  easy. This is better than nothing.
  
  See the previous change for a detailed explanation of the bug we've
  observed in the wild.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6555

AFFECTED FILES
  mercurial/vfs.py

CHANGE DETAILS

diff --git a/mercurial/vfs.py b/mercurial/vfs.py
--- a/mercurial/vfs.py
+++ b/mercurial/vfs.py
@@ -303,6 +303,53 @@
             finally:
                 vfs._backgroundfilecloser = None
 
+class _appendfileproxy(object):
+    """Proxy type to prevent .tell() before .seek() or .write() append files.
+
+    Files opened for append aren't required to have a meaningful fpos
+    after open, so we require a write or explicit seek before allowing
+    .tell() on those files.
+    """
+    def __init__(self, fp):
+        object.__setattr__(self, r'_fp', fp)
+        object.__setattr__(self, r'_tellok', False)
+
+    def tell(self):
+        if not self._tellok:
+            raise error.ProgrammingError(
+                'tell() on append-mode file requires write() or seek() first')
+        return self._fp.tell()
+
+    def seek(self, *args, **kwargs):
+        object.__setattr__(self, r'_tellok', True)
+        return self._fp.seek(*args, **kwargs)
+
+    def write(self, *args, **kwargs):
+        object.__setattr__(self, r'_tellok', True)
+        return self._fp.write(*args, **kwargs)
+
+    def __repr__(self):
+        return r'<_appendfileproxy of %r>' % self._fp
+
+    # __magic__ methods get looked up on the type, not the instance,
+    # so we have to proxy __enter__ and __exit__ by hand.
+    def __enter__(self):
+        self._fp.__enter__()
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        self._fp.__exit__(*args, **kwargs)
+
+    # proxy all other methods
+    def __getattr__(self, attr):
+        return getattr(self._fp, attr)
+
+    def __setattr__(self, attr, value):
+        return setattr(self._fp, attr, value)
+
+    def __delattr__(self, attr):
+        return delattr(self._fp, attr)
+
 class vfs(abstractvfs):
     '''Operate files relative to a base directory
 
@@ -441,7 +488,8 @@
                                   )
 
             fp = delayclosedfile(fp, self._backgroundfilecloser)
-
+        if mode in ('a', 'ab'):
+            return _appendfileproxy(fp)
         return fp
 
     def symlink(self, src, dst):



To: durin42, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list