mercurial/posix.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    39     # vaguely unix-like but don't have hardlink support. For those
    39     # vaguely unix-like but don't have hardlink support. For those
    40     # poor souls, just say we tried and that it failed so we fall back
    40     # poor souls, just say we tried and that it failed so we fall back
    41     # to copies.
    41     # to copies.
    42     def oslink(src, dst):
    42     def oslink(src, dst):
    43         raise OSError(
    43         raise OSError(
    44             errno.EINVAL, 'hardlinks not supported: %s to %s' % (src, dst)
    44             errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
    45         )
    45         )
    46 
    46 
    47 
    47 
    48 readlink = os.readlink
    48 readlink = os.readlink
    49 unlink = os.unlink
    49 unlink = os.unlink
    83     ...           b'/file_at_root',
    83     ...           b'/file_at_root',
    84     ...           b'///multiple_leading_separators_at_root',
    84     ...           b'///multiple_leading_separators_at_root',
    85     ...           b'']:
    85     ...           b'']:
    86     ...     assert split(f) == posixpath.split(f), f
    86     ...     assert split(f) == posixpath.split(f), f
    87     '''
    87     '''
    88     ht = p.rsplit('/', 1)
    88     ht = p.rsplit(b'/', 1)
    89     if len(ht) == 1:
    89     if len(ht) == 1:
    90         return '', p
    90         return b'', p
    91     nh = ht[0].rstrip('/')
    91     nh = ht[0].rstrip(b'/')
    92     if nh:
    92     if nh:
    93         return nh, ht[1]
    93         return nh, ht[1]
    94     return ht[0] + '/', ht[1]
    94     return ht[0] + b'/', ht[1]
    95 
    95 
    96 
    96 
    97 def openhardlinks():
    97 def openhardlinks():
    98     '''return true if it is safe to hold open file handles to hardlinks'''
    98     '''return true if it is safe to hold open file handles to hardlinks'''
    99     return True
    99     return True
   105 
   105 
   106 
   106 
   107 def parsepatchoutput(output_line):
   107 def parsepatchoutput(output_line):
   108     """parses the output produced by patch and returns the filename"""
   108     """parses the output produced by patch and returns the filename"""
   109     pf = output_line[14:]
   109     pf = output_line[14:]
   110     if pycompat.sysplatform == 'OpenVMS':
   110     if pycompat.sysplatform == b'OpenVMS':
   111         if pf[0] == '`':
   111         if pf[0] == b'`':
   112             pf = pf[1:-1]  # Remove the quotes
   112             pf = pf[1:-1]  # Remove the quotes
   113     else:
   113     else:
   114         if pf.startswith("'") and pf.endswith("'") and " " in pf:
   114         if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
   115             pf = pf[1:-1]  # Remove the quotes
   115             pf = pf[1:-1]  # Remove the quotes
   116     return pf
   116     return pf
   117 
   117 
   118 
   118 
   119 def sshargs(sshcmd, host, user, port):
   119 def sshargs(sshcmd, host, user, port):
   120     '''Build argument list for ssh'''
   120     '''Build argument list for ssh'''
   121     args = user and ("%s@%s" % (user, host)) or host
   121     args = user and (b"%s@%s" % (user, host)) or host
   122     if '-' in args[:1]:
   122     if b'-' in args[:1]:
   123         raise error.Abort(
   123         raise error.Abort(
   124             _('illegal ssh hostname or username starting with -: %s') % args
   124             _(b'illegal ssh hostname or username starting with -: %s') % args
   125         )
   125         )
   126     args = shellquote(args)
   126     args = shellquote(args)
   127     if port:
   127     if port:
   128         args = '-p %s %s' % (shellquote(port), args)
   128         args = b'-p %s %s' % (shellquote(port), args)
   129     return args
   129     return args
   130 
   130 
   131 
   131 
   132 def isexec(f):
   132 def isexec(f):
   133     """check whether a file is executable"""
   133     """check whether a file is executable"""
   138     st = os.lstat(f)
   138     st = os.lstat(f)
   139     s = st.st_mode
   139     s = st.st_mode
   140     if l:
   140     if l:
   141         if not stat.S_ISLNK(s):
   141         if not stat.S_ISLNK(s):
   142             # switch file to link
   142             # switch file to link
   143             fp = open(f, 'rb')
   143             fp = open(f, b'rb')
   144             data = fp.read()
   144             data = fp.read()
   145             fp.close()
   145             fp.close()
   146             unlink(f)
   146             unlink(f)
   147             try:
   147             try:
   148                 os.symlink(data, f)
   148                 os.symlink(data, f)
   149             except OSError:
   149             except OSError:
   150                 # failed to make a link, rewrite file
   150                 # failed to make a link, rewrite file
   151                 fp = open(f, "wb")
   151                 fp = open(f, b"wb")
   152                 fp.write(data)
   152                 fp.write(data)
   153                 fp.close()
   153                 fp.close()
   154         # no chmod needed at this point
   154         # no chmod needed at this point
   155         return
   155         return
   156     if stat.S_ISLNK(s):
   156     if stat.S_ISLNK(s):
   157         # switch link to file
   157         # switch link to file
   158         data = os.readlink(f)
   158         data = os.readlink(f)
   159         unlink(f)
   159         unlink(f)
   160         fp = open(f, "wb")
   160         fp = open(f, b"wb")
   161         fp.write(data)
   161         fp.write(data)
   162         fp.close()
   162         fp.close()
   163         s = 0o666 & ~umask  # avoid restatting for chmod
   163         s = 0o666 & ~umask  # avoid restatting for chmod
   164 
   164 
   165     sx = s & 0o100
   165     sx = s & 0o100
   166     if st.st_nlink > 1 and bool(x) != bool(sx):
   166     if st.st_nlink > 1 and bool(x) != bool(sx):
   167         # the file is a hardlink, break it
   167         # the file is a hardlink, break it
   168         with open(f, "rb") as fp:
   168         with open(f, b"rb") as fp:
   169             data = fp.read()
   169             data = fp.read()
   170         unlink(f)
   170         unlink(f)
   171         with open(f, "wb") as fp:
   171         with open(f, b"wb") as fp:
   172             fp.write(data)
   172             fp.write(data)
   173 
   173 
   174     if x and not sx:
   174     if x and not sx:
   175         # Turn on +x for every +r bit when making a file executable
   175         # Turn on +x for every +r bit when making a file executable
   176         # and obey umask.
   176         # and obey umask.
   213     # a FS remount. Frequently we can detect it if files are created
   213     # a FS remount. Frequently we can detect it if files are created
   214     # with exec bit on.
   214     # with exec bit on.
   215 
   215 
   216     try:
   216     try:
   217         EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
   217         EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
   218         basedir = os.path.join(path, '.hg')
   218         basedir = os.path.join(path, b'.hg')
   219         cachedir = os.path.join(basedir, 'wcache')
   219         cachedir = os.path.join(basedir, b'wcache')
   220         storedir = os.path.join(basedir, 'store')
   220         storedir = os.path.join(basedir, b'store')
   221         if not os.path.exists(cachedir):
   221         if not os.path.exists(cachedir):
   222             try:
   222             try:
   223                 # we want to create the 'cache' directory, not the '.hg' one.
   223                 # we want to create the 'cache' directory, not the '.hg' one.
   224                 # Automatically creating '.hg' directory could silently spawn
   224                 # Automatically creating '.hg' directory could silently spawn
   225                 # invalid Mercurial repositories. That seems like a bad idea.
   225                 # invalid Mercurial repositories. That seems like a bad idea.
   230                     copymode(basedir, cachedir)
   230                     copymode(basedir, cachedir)
   231             except (IOError, OSError):
   231             except (IOError, OSError):
   232                 # we other fallback logic triggers
   232                 # we other fallback logic triggers
   233                 pass
   233                 pass
   234         if os.path.isdir(cachedir):
   234         if os.path.isdir(cachedir):
   235             checkisexec = os.path.join(cachedir, 'checkisexec')
   235             checkisexec = os.path.join(cachedir, b'checkisexec')
   236             checknoexec = os.path.join(cachedir, 'checknoexec')
   236             checknoexec = os.path.join(cachedir, b'checknoexec')
   237 
   237 
   238             try:
   238             try:
   239                 m = os.stat(checkisexec).st_mode
   239                 m = os.stat(checkisexec).st_mode
   240             except OSError as e:
   240             except OSError as e:
   241                 if e.errno != errno.ENOENT:
   241                 if e.errno != errno.ENOENT:
   248                     try:
   248                     try:
   249                         m = os.stat(checknoexec).st_mode
   249                         m = os.stat(checknoexec).st_mode
   250                     except OSError as e:
   250                     except OSError as e:
   251                         if e.errno != errno.ENOENT:
   251                         if e.errno != errno.ENOENT:
   252                             raise
   252                             raise
   253                         open(checknoexec, 'w').close()  # might fail
   253                         open(checknoexec, b'w').close()  # might fail
   254                         m = os.stat(checknoexec).st_mode
   254                         m = os.stat(checknoexec).st_mode
   255                     if m & EXECFLAGS == 0:
   255                     if m & EXECFLAGS == 0:
   256                         # check-exec is exec and check-no-exec is not exec
   256                         # check-exec is exec and check-no-exec is not exec
   257                         return True
   257                         return True
   258                     # checknoexec exists but is exec - delete it
   258                     # checknoexec exists but is exec - delete it
   264             checkdir = cachedir
   264             checkdir = cachedir
   265         else:
   265         else:
   266             # check directly in path and don't leave checkisexec behind
   266             # check directly in path and don't leave checkisexec behind
   267             checkdir = path
   267             checkdir = path
   268             checkisexec = None
   268             checkisexec = None
   269         fh, fn = pycompat.mkstemp(dir=checkdir, prefix='hg-checkexec-')
   269         fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
   270         try:
   270         try:
   271             os.close(fh)
   271             os.close(fh)
   272             m = os.stat(fn).st_mode
   272             m = os.stat(fn).st_mode
   273             if m & EXECFLAGS == 0:
   273             if m & EXECFLAGS == 0:
   274                 os.chmod(fn, m & 0o777 | EXECFLAGS)
   274                 os.chmod(fn, m & 0o777 | EXECFLAGS)
   288 def checklink(path):
   288 def checklink(path):
   289     """check whether the given path is on a symlink-capable filesystem"""
   289     """check whether the given path is on a symlink-capable filesystem"""
   290     # mktemp is not racy because symlink creation will fail if the
   290     # mktemp is not racy because symlink creation will fail if the
   291     # file already exists
   291     # file already exists
   292     while True:
   292     while True:
   293         cachedir = os.path.join(path, '.hg', 'wcache')
   293         cachedir = os.path.join(path, b'.hg', b'wcache')
   294         checklink = os.path.join(cachedir, 'checklink')
   294         checklink = os.path.join(cachedir, b'checklink')
   295         # try fast path, read only
   295         # try fast path, read only
   296         if os.path.islink(checklink):
   296         if os.path.islink(checklink):
   297             return True
   297             return True
   298         if os.path.isdir(cachedir):
   298         if os.path.isdir(cachedir):
   299             checkdir = cachedir
   299             checkdir = cachedir
   306         name = pycompat.fsencode(name)
   306         name = pycompat.fsencode(name)
   307         try:
   307         try:
   308             fd = None
   308             fd = None
   309             if cachedir is None:
   309             if cachedir is None:
   310                 fd = pycompat.namedtempfile(
   310                 fd = pycompat.namedtempfile(
   311                     dir=checkdir, prefix='hg-checklink-'
   311                     dir=checkdir, prefix=b'hg-checklink-'
   312                 )
   312                 )
   313                 target = os.path.basename(fd.name)
   313                 target = os.path.basename(fd.name)
   314             else:
   314             else:
   315                 # create a fixed file to link to; doesn't matter if it
   315                 # create a fixed file to link to; doesn't matter if it
   316                 # already exists.
   316                 # already exists.
   317                 target = 'checklink-target'
   317                 target = b'checklink-target'
   318                 try:
   318                 try:
   319                     fullpath = os.path.join(cachedir, target)
   319                     fullpath = os.path.join(cachedir, target)
   320                     open(fullpath, 'w').close()
   320                     open(fullpath, b'w').close()
   321                 except IOError as inst:
   321                 except IOError as inst:
   322                     if inst[0] == errno.EACCES:
   322                     if inst[0] == errno.EACCES:
   323                         # If we can't write to cachedir, just pretend
   323                         # If we can't write to cachedir, just pretend
   324                         # that the fs is readonly and by association
   324                         # that the fs is readonly and by association
   325                         # that the fs won't support symlinks. This
   325                         # that the fs won't support symlinks. This
   442     def normcasefallback(path):
   442     def normcasefallback(path):
   443         try:
   443         try:
   444             u = path.decode('utf-8')
   444             u = path.decode('utf-8')
   445         except UnicodeDecodeError:
   445         except UnicodeDecodeError:
   446             # OS X percent-encodes any bytes that aren't valid utf-8
   446             # OS X percent-encodes any bytes that aren't valid utf-8
   447             s = ''
   447             s = b''
   448             pos = 0
   448             pos = 0
   449             l = len(path)
   449             l = len(path)
   450             while pos < l:
   450             while pos < l:
   451                 try:
   451                 try:
   452                     c = encoding.getutf8char(path, pos)
   452                     c = encoding.getutf8char(path, pos)
   453                     pos += len(c)
   453                     pos += len(c)
   454                 except ValueError:
   454                 except ValueError:
   455                     c = '%%%02X' % ord(path[pos : pos + 1])
   455                     c = b'%%%02X' % ord(path[pos : pos + 1])
   456                     pos += 1
   456                     pos += 1
   457                 s += c
   457                 s += c
   458 
   458 
   459             u = s.decode('utf-8')
   459             u = s.decode('utf-8')
   460 
   460 
   462         enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
   462         enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
   463         # drop HFS+ ignored characters
   463         # drop HFS+ ignored characters
   464         return encoding.hfsignoreclean(enc)
   464         return encoding.hfsignoreclean(enc)
   465 
   465 
   466 
   466 
   467 if pycompat.sysplatform == 'cygwin':
   467 if pycompat.sysplatform == b'cygwin':
   468     # workaround for cygwin, in which mount point part of path is
   468     # workaround for cygwin, in which mount point part of path is
   469     # treated as case sensitive, even though underlying NTFS is case
   469     # treated as case sensitive, even though underlying NTFS is case
   470     # insensitive.
   470     # insensitive.
   471 
   471 
   472     # default mount points
   472     # default mount points
   473     cygwinmountpoints = sorted(
   473     cygwinmountpoints = sorted(
   474         ["/usr/bin", "/usr/lib", "/cygdrive",], reverse=True
   474         [b"/usr/bin", b"/usr/lib", b"/cygdrive",], reverse=True
   475     )
   475     )
   476 
   476 
   477     # use upper-ing as normcase as same as NTFS workaround
   477     # use upper-ing as normcase as same as NTFS workaround
   478     def normcase(path):
   478     def normcase(path):
   479         pathlen = len(path)
   479         pathlen = len(path)
   513 
   513 
   514 _needsshellquote = None
   514 _needsshellquote = None
   515 
   515 
   516 
   516 
   517 def shellquote(s):
   517 def shellquote(s):
   518     if pycompat.sysplatform == 'OpenVMS':
   518     if pycompat.sysplatform == b'OpenVMS':
   519         return '"%s"' % s
   519         return b'"%s"' % s
   520     global _needsshellquote
   520     global _needsshellquote
   521     if _needsshellquote is None:
   521     if _needsshellquote is None:
   522         _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
   522         _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
   523     if s and not _needsshellquote(s):
   523     if s and not _needsshellquote(s):
   524         # "s" shouldn't have to be quoted
   524         # "s" shouldn't have to be quoted
   525         return s
   525         return s
   526     else:
   526     else:
   527         return "'%s'" % s.replace("'", "'\\''")
   527         return b"'%s'" % s.replace(b"'", b"'\\''")
   528 
   528 
   529 
   529 
   530 def shellsplit(s):
   530 def shellsplit(s):
   531     """Parse a command string in POSIX shell way (best-effort)"""
   531     """Parse a command string in POSIX shell way (best-effort)"""
   532     return pycompat.shlexsplit(s, posix=True)
   532     return pycompat.shlexsplit(s, posix=True)
   536     return cmd
   536     return cmd
   537 
   537 
   538 
   538 
   539 def testpid(pid):
   539 def testpid(pid):
   540     '''return False if pid dead, True if running or not sure'''
   540     '''return False if pid dead, True if running or not sure'''
   541     if pycompat.sysplatform == 'OpenVMS':
   541     if pycompat.sysplatform == b'OpenVMS':
   542         return True
   542         return True
   543     try:
   543     try:
   544         os.kill(pid, 0)
   544         os.kill(pid, 0)
   545         return True
   545         return True
   546     except OSError as inst:
   546     except OSError as inst:
   555 def findexe(command):
   555 def findexe(command):
   556     '''Find executable for command searching like which does.
   556     '''Find executable for command searching like which does.
   557     If command is a basename then PATH is searched for command.
   557     If command is a basename then PATH is searched for command.
   558     PATH isn't searched if command is an absolute or relative path.
   558     PATH isn't searched if command is an absolute or relative path.
   559     If command isn't found None is returned.'''
   559     If command isn't found None is returned.'''
   560     if pycompat.sysplatform == 'OpenVMS':
   560     if pycompat.sysplatform == b'OpenVMS':
   561         return command
   561         return command
   562 
   562 
   563     def findexisting(executable):
   563     def findexisting(executable):
   564         'Will return executable if existing file'
   564         b'Will return executable if existing file'
   565         if os.path.isfile(executable) and os.access(executable, os.X_OK):
   565         if os.path.isfile(executable) and os.access(executable, os.X_OK):
   566             return executable
   566             return executable
   567         return None
   567         return None
   568 
   568 
   569     if pycompat.ossep in command:
   569     if pycompat.ossep in command:
   570         return findexisting(command)
   570         return findexisting(command)
   571 
   571 
   572     if pycompat.sysplatform == 'plan9':
   572     if pycompat.sysplatform == b'plan9':
   573         return findexisting(os.path.join('/bin', command))
   573         return findexisting(os.path.join(b'/bin', command))
   574 
   574 
   575     for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
   575     for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
   576         executable = findexisting(os.path.join(path, command))
   576         executable = findexisting(os.path.join(path, command))
   577         if executable is not None:
   577         if executable is not None:
   578             return executable
   578             return executable
   579     return None
   579     return None
   580 
   580 
   750                     break
   750                     break
   751                 chunks.append(s)
   751                 chunks.append(s)
   752             except IOError:
   752             except IOError:
   753                 break
   753                 break
   754 
   754 
   755         return ''.join(chunks)
   755         return b''.join(chunks)
   756     finally:
   756     finally:
   757         fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
   757         fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
   758 
   758 
   759 
   759 
   760 def bindunixsocket(sock, path):
   760 def bindunixsocket(sock, path):
   763     # AF_UNIX path has very small length limit (107 chars) on common
   763     # AF_UNIX path has very small length limit (107 chars) on common
   764     # platforms (see sys/un.h)
   764     # platforms (see sys/un.h)
   765     dirname, basename = os.path.split(path)
   765     dirname, basename = os.path.split(path)
   766     bakwdfd = None
   766     bakwdfd = None
   767     if dirname:
   767     if dirname:
   768         bakwdfd = os.open('.', os.O_DIRECTORY)
   768         bakwdfd = os.open(b'.', os.O_DIRECTORY)
   769         os.chdir(dirname)
   769         os.chdir(dirname)
   770     sock.bind(basename)
   770     sock.bind(basename)
   771     if bakwdfd:
   771     if bakwdfd:
   772         os.fchdir(bakwdfd)
   772         os.fchdir(bakwdfd)
   773         os.close(bakwdfd)
   773         os.close(bakwdfd)