On 2016-08-25 20:11, tt...@fb.com wrote:
> # HG changeset patch
> # User Tony Tung <tonyt...@merly.org>
> # Date 1472148645 25200
> #      Thu Aug 25 11:10:45 2016 -0700
> # Node ID 625f3bb4833b47c4d35754fced91f37b077f7866
> # Parent  2efc5a05d80a6d4253767f2ce0c2fb062ba83cb6
> util: move checknlink away from the dependency of a hard-coded filename
> 
> This somewhat insulates us against bugs in checknlink when a stale file
> left behind could result in checknlink always returning False.
> 
> diff --git a/mercurial/util.py b/mercurial/util.py
> --- a/mercurial/util.py
> +++ b/mercurial/util.py
> @@ -1305,38 +1305,53 @@
>  def checknlink(testfile):
>      '''check whether hardlink count reporting works properly'''
>  
> -    # testfile may be open, so we need a separate file for checking to
> -    # work around issue2543 (or testfile may get lost on Samba shares)
> -    f1 = testfile + ".hgtmp1"
> -    if os.path.lexists(f1):
> -        return False
> +    f1 = {}
> +    f2 = {}
> +
>      try:
> -        posixfile(f1, 'w').close()
> -    except IOError:
> -        try:
> -            os.unlink(f1)
> -        except OSError:
> -            pass
> -        return False
> -
> -    f2 = testfile + ".hgtmp2"
> -    fd = None
> -    try:
> -        oslink(f1, f2)
> +        dirpath, filename = os.path.split(testfile)
> +
> +        # testfile may be open, so we need a separate file for checking to
> +        # work around issue2543 (or testfile may get lost on Samba shares)
> +        f1['fd'], f1['path'] = tempfile.mkstemp(prefix=filename, dir=dirpath)
> +        os.close(f1['fd'])
> +        del f1['fd']
> +
> +        f2['fd'], f2['path'] = tempfile.mkstemp(prefix=filename, dir=dirpath)
> +        os.close(f2['fd'])
> +        del f2['fd']
> +
> +        # there's a small race condition that another file can jam itself in
> +        # between the time we remove f2 and the time we create the hard link.
> +        # in the unlikely scenario that happens, we'll treat it as nlink 
> being
> +        # unreliable.
> +        os.unlink(f2['path'])
> +        oslink(f1['path'], f2['path'])
>          # nlinks() may behave differently for files on Windows shares if
>          # the file is open.
> -        fd = posixfile(f2)
> -        return nlinks(f2) > 1
> -    except OSError:
> +        f2['handle'] = posixfile(f2['path'])
> +        return nlinks(f2['path']) > 1
> +    except EnvironmentError:
>          return False
>      finally:
> -        if fd is not None:
> -            fd.close()
> -        for f in (f1, f2):
> -            try:
> -                os.unlink(f)
> -            except OSError:
> -                pass
> +        for fi in (f1, f2):
> +            if 'fd' in fi:
> +                try:
> +                    os.close(fi['fd'])
> +                except EnvironmentError:
> +                    pass
> +
> +            if 'handle' in fi:
> +                try:
> +                    fi['handle'].close()
> +                except EnvironmentError:
> +                    pass
> +
> +            if 'path' in fi:
> +                try:
> +                    os.unlink(fi['path'])
> +                except EnvironmentError:
> +                    pass

If I'm not mistaken, this will try to unlink file f1 while hardlinked
alias name f2 still has an open handle (opened using posixfile).

This may not be a problem with posixfile, but Windows can have problems,
if a hardlinked file is deleted while it is still in open() state under
an alias name (see [1]).

I would thus recommend closing all files before unlinking.

I'd additionally prefer doing cleanup things in reverse order, i.e.
close 'handle' on f2 first.

So, I think I'd prefer doing the cleanup this way:

        # close files
        for fi in (f2, f1): # intentionally in reverse order
            if 'handle' in fi:
                try:
                    fi['handle'].close()
                except EnvironmentError:
                    pass
            if 'fd' in fi:
                try:
                    os.close(fi['fd'])
                except EnvironmentError:
                    pass

        # unlink files
        for fi in (f2, f1):  # intentionally in reverse order
            if 'path' in fi:
                try:
                    os.unlink(fi['path'])
                except EnvironmentError:
                    pass

>  
>  def endswithsep(path):
>      '''Check path ends with os.sep or os.altsep.'''

[1] https://www.mercurial-scm.org/wiki/UnlinkingFilesOnWindows
_______________________________________________
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Reply via email to