comparison mercurial/util.py @ 12938:bf826c0b9537 stable

opener: check hardlink count reporting (issue1866) The Linux CIFS kernel driver (even in 2.6.36) suffers from a hardlink count blindness bug (lstat() returning 1 in st_nlink when it is expected to return >1), which causes repository corruption if Mercurial running on Linux pushes or commits to a hardlinked repository stored on a Windows share, if that share is mounted using the CIFS driver. This patch works around issue1866 and improves the workaround done in 50523b4407f6 to fix issue761, by teaching the opener to lazily execute a runtime check (new function checknlink) to see if the hardlink count reported by nlinks() can be trusted. Since nlinks() is also known to return varying count values (1 or >1) depending on whether the file is open or not and depending on what client and server software combination is being used for accessing and serving the Windows share, we deliberately open the file before calling nlinks() in order to have a stable precondition. Trying to depend on the precondition "file closed" would be fragile, as the file could have been opened very easily somewhere else in the program.
author Adrian Buehlmann <adrian@cadifra.com>
date Sun, 07 Nov 2010 18:21:29 +0100
parents 6ff784de7c3a
children 9f2ac318b92e 670f4e98276d
comparison
equal deleted inserted replaced
12937:6ff784de7c3a 12938:bf826c0b9537
714 os.unlink(name) 714 os.unlink(name)
715 return True 715 return True
716 except (OSError, AttributeError): 716 except (OSError, AttributeError):
717 return False 717 return False
718 718
719 def checknlink(testfile):
720 '''check whether hardlink count reporting works properly'''
721 f = testfile + ".hgtmp"
722
723 try:
724 os_link(testfile, f)
725 except OSError, inst:
726 if inst.errno == errno.EINVAL:
727 # FS doesn't support creating hardlinks
728 return True
729 return False
730
731 try:
732 # nlinks() may behave differently for files on Windows shares if
733 # the file is open.
734 fd = open(f)
735 return nlinks(f) > 1
736 finally:
737 fd.close()
738 os.unlink(f)
739
740 return False
741
719 def endswithsep(path): 742 def endswithsep(path):
720 '''Check path ends with os.sep or os.altsep.''' 743 '''Check path ends with os.sep or os.altsep.'''
721 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep) 744 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
722 745
723 def splitpath(path): 746 def splitpath(path):
838 if audit: 861 if audit:
839 self.auditor = path_auditor(base) 862 self.auditor = path_auditor(base)
840 else: 863 else:
841 self.auditor = always 864 self.auditor = always
842 self.createmode = None 865 self.createmode = None
866 self._trustnlink = None
843 867
844 @propertycache 868 @propertycache
845 def _can_symlink(self): 869 def _can_symlink(self):
846 return checklink(self.base) 870 return checklink(self.base)
847 871
871 if 'w' in mode: 895 if 'w' in mode:
872 st_mode = os.lstat(f).st_mode & 0777 896 st_mode = os.lstat(f).st_mode & 0777
873 os.unlink(f) 897 os.unlink(f)
874 nlink = 0 898 nlink = 0
875 else: 899 else:
900 # nlinks() may behave differently for files on Windows
901 # shares if the file is open.
902 fd = open(f)
876 nlink = nlinks(f) 903 nlink = nlinks(f)
877 except OSError: 904 fd.close()
905 except (OSError, IOError):
878 nlink = 0 906 nlink = 0
879 if not os.path.isdir(dirname): 907 if not os.path.isdir(dirname):
880 makedirs(dirname, self.createmode) 908 makedirs(dirname, self.createmode)
881 if nlink > 1: 909 if nlink > 0:
882 rename(mktempcopy(f), f) 910 if self._trustnlink is None:
911 self._trustnlink = nlink > 1 or checknlink(f)
912 if nlink > 1 or not self._trustnlink:
913 rename(mktempcopy(f), f)
883 fp = posixfile(f, mode) 914 fp = posixfile(f, mode)
884 if nlink == 0: 915 if nlink == 0:
885 if st_mode is None: 916 if st_mode is None:
886 self._fixfilemode(f) 917 self._fixfilemode(f)
887 else: 918 else: