PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/mercurial/posix.py

https://bitbucket.org/mirror/mercurial/
Python | 569 lines | 520 code | 20 blank | 29 comment | 26 complexity | a91436492ab9df7b732dbf6c322a92d9 MD5 | raw file
Possible License(s): GPL-2.0
  1. # posix.py - Posix utility function implementations for Mercurial
  2. #
  3. # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. from i18n import _
  8. import encoding
  9. import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
  10. posixfile = open
  11. normpath = os.path.normpath
  12. samestat = os.path.samestat
  13. oslink = os.link
  14. unlink = os.unlink
  15. rename = os.rename
  16. expandglobs = False
  17. umask = os.umask(0)
  18. os.umask(umask)
  19. def split(p):
  20. '''Same as posixpath.split, but faster
  21. >>> import posixpath
  22. >>> for f in ['/absolute/path/to/file',
  23. ... 'relative/path/to/file',
  24. ... 'file_alone',
  25. ... 'path/to/directory/',
  26. ... '/multiple/path//separators',
  27. ... '/file_at_root',
  28. ... '///multiple_leading_separators_at_root',
  29. ... '']:
  30. ... assert split(f) == posixpath.split(f), f
  31. '''
  32. ht = p.rsplit('/', 1)
  33. if len(ht) == 1:
  34. return '', p
  35. nh = ht[0].rstrip('/')
  36. if nh:
  37. return nh, ht[1]
  38. return ht[0] + '/', ht[1]
  39. def openhardlinks():
  40. '''return true if it is safe to hold open file handles to hardlinks'''
  41. return True
  42. def nlinks(name):
  43. '''return number of hardlinks for the given file'''
  44. return os.lstat(name).st_nlink
  45. def parsepatchoutput(output_line):
  46. """parses the output produced by patch and returns the filename"""
  47. pf = output_line[14:]
  48. if os.sys.platform == 'OpenVMS':
  49. if pf[0] == '`':
  50. pf = pf[1:-1] # Remove the quotes
  51. else:
  52. if pf.startswith("'") and pf.endswith("'") and " " in pf:
  53. pf = pf[1:-1] # Remove the quotes
  54. return pf
  55. def sshargs(sshcmd, host, user, port):
  56. '''Build argument list for ssh'''
  57. args = user and ("%s@%s" % (user, host)) or host
  58. return port and ("%s -p %s" % (args, port)) or args
  59. def isexec(f):
  60. """check whether a file is executable"""
  61. return (os.lstat(f).st_mode & 0100 != 0)
  62. def setflags(f, l, x):
  63. s = os.lstat(f).st_mode
  64. if l:
  65. if not stat.S_ISLNK(s):
  66. # switch file to link
  67. fp = open(f)
  68. data = fp.read()
  69. fp.close()
  70. os.unlink(f)
  71. try:
  72. os.symlink(data, f)
  73. except OSError:
  74. # failed to make a link, rewrite file
  75. fp = open(f, "w")
  76. fp.write(data)
  77. fp.close()
  78. # no chmod needed at this point
  79. return
  80. if stat.S_ISLNK(s):
  81. # switch link to file
  82. data = os.readlink(f)
  83. os.unlink(f)
  84. fp = open(f, "w")
  85. fp.write(data)
  86. fp.close()
  87. s = 0666 & ~umask # avoid restatting for chmod
  88. sx = s & 0100
  89. if x and not sx:
  90. # Turn on +x for every +r bit when making a file executable
  91. # and obey umask.
  92. os.chmod(f, s | (s & 0444) >> 2 & ~umask)
  93. elif not x and sx:
  94. # Turn off all +x bits
  95. os.chmod(f, s & 0666)
  96. def copymode(src, dst, mode=None):
  97. '''Copy the file mode from the file at path src to dst.
  98. If src doesn't exist, we're using mode instead. If mode is None, we're
  99. using umask.'''
  100. try:
  101. st_mode = os.lstat(src).st_mode & 0777
  102. except OSError, inst:
  103. if inst.errno != errno.ENOENT:
  104. raise
  105. st_mode = mode
  106. if st_mode is None:
  107. st_mode = ~umask
  108. st_mode &= 0666
  109. os.chmod(dst, st_mode)
  110. def checkexec(path):
  111. """
  112. Check whether the given path is on a filesystem with UNIX-like exec flags
  113. Requires a directory (like /foo/.hg)
  114. """
  115. # VFAT on some Linux versions can flip mode but it doesn't persist
  116. # a FS remount. Frequently we can detect it if files are created
  117. # with exec bit on.
  118. try:
  119. EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
  120. fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
  121. try:
  122. os.close(fh)
  123. m = os.stat(fn).st_mode & 0777
  124. new_file_has_exec = m & EXECFLAGS
  125. os.chmod(fn, m ^ EXECFLAGS)
  126. exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
  127. finally:
  128. os.unlink(fn)
  129. except (IOError, OSError):
  130. # we don't care, the user probably won't be able to commit anyway
  131. return False
  132. return not (new_file_has_exec or exec_flags_cannot_flip)
  133. def checklink(path):
  134. """check whether the given path is on a symlink-capable filesystem"""
  135. # mktemp is not racy because symlink creation will fail if the
  136. # file already exists
  137. name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
  138. try:
  139. fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
  140. os.symlink(os.path.basename(fd.name), name)
  141. os.unlink(name)
  142. return True
  143. except AttributeError:
  144. return False
  145. except OSError, inst:
  146. # sshfs might report failure while successfully creating the link
  147. if inst[0] == errno.EIO and os.path.exists(name):
  148. os.unlink(name)
  149. return False
  150. def checkosfilename(path):
  151. '''Check that the base-relative path is a valid filename on this platform.
  152. Returns None if the path is ok, or a UI string describing the problem.'''
  153. pass # on posix platforms, every path is ok
  154. def setbinary(fd):
  155. pass
  156. def pconvert(path):
  157. return path
  158. def localpath(path):
  159. return path
  160. def samefile(fpath1, fpath2):
  161. """Returns whether path1 and path2 refer to the same file. This is only
  162. guaranteed to work for files, not directories."""
  163. return os.path.samefile(fpath1, fpath2)
  164. def samedevice(fpath1, fpath2):
  165. """Returns whether fpath1 and fpath2 are on the same device. This is only
  166. guaranteed to work for files, not directories."""
  167. st1 = os.lstat(fpath1)
  168. st2 = os.lstat(fpath2)
  169. return st1.st_dev == st2.st_dev
  170. # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
  171. def normcase(path):
  172. return path.lower()
  173. if sys.platform == 'darwin':
  174. def normcase(path):
  175. '''
  176. Normalize a filename for OS X-compatible comparison:
  177. - escape-encode invalid characters
  178. - decompose to NFD
  179. - lowercase
  180. >>> normcase('UPPER')
  181. 'upper'
  182. >>> normcase('Caf\xc3\xa9')
  183. 'cafe\\xcc\\x81'
  184. >>> normcase('\xc3\x89')
  185. 'e\\xcc\\x81'
  186. >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
  187. '%b8%ca%c3\\xca\\xbe%c8.jpg'
  188. '''
  189. try:
  190. path.decode('ascii') # throw exception for non-ASCII character
  191. return path.lower()
  192. except UnicodeDecodeError:
  193. pass
  194. try:
  195. u = path.decode('utf-8')
  196. except UnicodeDecodeError:
  197. # OS X percent-encodes any bytes that aren't valid utf-8
  198. s = ''
  199. g = ''
  200. l = 0
  201. for c in path:
  202. o = ord(c)
  203. if l and o < 128 or o >= 192:
  204. # we want a continuation byte, but didn't get one
  205. s += ''.join(["%%%02X" % ord(x) for x in g])
  206. g = ''
  207. l = 0
  208. if l == 0 and o < 128:
  209. # ascii
  210. s += c
  211. elif l == 0 and 194 <= o < 245:
  212. # valid leading bytes
  213. if o < 224:
  214. l = 1
  215. elif o < 240:
  216. l = 2
  217. else:
  218. l = 3
  219. g = c
  220. elif l > 0 and 128 <= o < 192:
  221. # valid continuations
  222. g += c
  223. l -= 1
  224. if not l:
  225. s += g
  226. g = ''
  227. else:
  228. # invalid
  229. s += "%%%02X" % o
  230. # any remaining partial characters
  231. s += ''.join(["%%%02X" % ord(x) for x in g])
  232. u = s.decode('utf-8')
  233. # Decompose then lowercase (HFS+ technote specifies lower)
  234. return unicodedata.normalize('NFD', u).lower().encode('utf-8')
  235. if sys.platform == 'cygwin':
  236. # workaround for cygwin, in which mount point part of path is
  237. # treated as case sensitive, even though underlying NTFS is case
  238. # insensitive.
  239. # default mount points
  240. cygwinmountpoints = sorted([
  241. "/usr/bin",
  242. "/usr/lib",
  243. "/cygdrive",
  244. ], reverse=True)
  245. # use upper-ing as normcase as same as NTFS workaround
  246. def normcase(path):
  247. pathlen = len(path)
  248. if (pathlen == 0) or (path[0] != os.sep):
  249. # treat as relative
  250. return encoding.upper(path)
  251. # to preserve case of mountpoint part
  252. for mp in cygwinmountpoints:
  253. if not path.startswith(mp):
  254. continue
  255. mplen = len(mp)
  256. if mplen == pathlen: # mount point itself
  257. return mp
  258. if path[mplen] == os.sep:
  259. return mp + encoding.upper(path[mplen:])
  260. return encoding.upper(path)
  261. # Cygwin translates native ACLs to POSIX permissions,
  262. # but these translations are not supported by native
  263. # tools, so the exec bit tends to be set erroneously.
  264. # Therefore, disable executable bit access on Cygwin.
  265. def checkexec(path):
  266. return False
  267. # Similarly, Cygwin's symlink emulation is likely to create
  268. # problems when Mercurial is used from both Cygwin and native
  269. # Windows, with other native tools, or on shared volumes
  270. def checklink(path):
  271. return False
  272. def shellquote(s):
  273. if os.sys.platform == 'OpenVMS':
  274. return '"%s"' % s
  275. else:
  276. return "'%s'" % s.replace("'", "'\\''")
  277. def quotecommand(cmd):
  278. return cmd
  279. def popen(command, mode='r'):
  280. return os.popen(command, mode)
  281. def testpid(pid):
  282. '''return False if pid dead, True if running or not sure'''
  283. if os.sys.platform == 'OpenVMS':
  284. return True
  285. try:
  286. os.kill(pid, 0)
  287. return True
  288. except OSError, inst:
  289. return inst.errno != errno.ESRCH
  290. def explainexit(code):
  291. """return a 2-tuple (desc, code) describing a subprocess status
  292. (codes from kill are negative - not os.system/wait encoding)"""
  293. if code >= 0:
  294. return _("exited with status %d") % code, code
  295. return _("killed by signal %d") % -code, -code
  296. def isowner(st):
  297. """Return True if the stat object st is from the current user."""
  298. return st.st_uid == os.getuid()
  299. def findexe(command):
  300. '''Find executable for command searching like which does.
  301. If command is a basename then PATH is searched for command.
  302. PATH isn't searched if command is an absolute or relative path.
  303. If command isn't found None is returned.'''
  304. if sys.platform == 'OpenVMS':
  305. return command
  306. def findexisting(executable):
  307. 'Will return executable if existing file'
  308. if os.path.isfile(executable) and os.access(executable, os.X_OK):
  309. return executable
  310. return None
  311. if os.sep in command:
  312. return findexisting(command)
  313. if sys.platform == 'plan9':
  314. return findexisting(os.path.join('/bin', command))
  315. for path in os.environ.get('PATH', '').split(os.pathsep):
  316. executable = findexisting(os.path.join(path, command))
  317. if executable is not None:
  318. return executable
  319. return None
  320. def setsignalhandler():
  321. pass
  322. _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
  323. def statfiles(files):
  324. '''Stat each file in files. Yield each stat, or None if a file does not
  325. exist or has a type we don't care about.'''
  326. lstat = os.lstat
  327. getkind = stat.S_IFMT
  328. for nf in files:
  329. try:
  330. st = lstat(nf)
  331. if getkind(st.st_mode) not in _wantedkinds:
  332. st = None
  333. except OSError, err:
  334. if err.errno not in (errno.ENOENT, errno.ENOTDIR):
  335. raise
  336. st = None
  337. yield st
  338. def getuser():
  339. '''return name of current user'''
  340. return getpass.getuser()
  341. def username(uid=None):
  342. """Return the name of the user with the given uid.
  343. If uid is None, return the name of the current user."""
  344. if uid is None:
  345. uid = os.getuid()
  346. try:
  347. return pwd.getpwuid(uid)[0]
  348. except KeyError:
  349. return str(uid)
  350. def groupname(gid=None):
  351. """Return the name of the group with the given gid.
  352. If gid is None, return the name of the current group."""
  353. if gid is None:
  354. gid = os.getgid()
  355. try:
  356. return grp.getgrgid(gid)[0]
  357. except KeyError:
  358. return str(gid)
  359. def groupmembers(name):
  360. """Return the list of members of the group with the given
  361. name, KeyError if the group does not exist.
  362. """
  363. return list(grp.getgrnam(name).gr_mem)
  364. def spawndetached(args):
  365. return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
  366. args[0], args)
  367. def gethgcmd():
  368. return sys.argv[:1]
  369. def termwidth():
  370. try:
  371. import termios, array, fcntl
  372. for dev in (sys.stderr, sys.stdout, sys.stdin):
  373. try:
  374. try:
  375. fd = dev.fileno()
  376. except AttributeError:
  377. continue
  378. if not os.isatty(fd):
  379. continue
  380. try:
  381. arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
  382. width = array.array('h', arri)[1]
  383. if width > 0:
  384. return width
  385. except AttributeError:
  386. pass
  387. except ValueError:
  388. pass
  389. except IOError, e:
  390. if e[0] == errno.EINVAL:
  391. pass
  392. else:
  393. raise
  394. except ImportError:
  395. pass
  396. return 80
  397. def makedir(path, notindexed):
  398. os.mkdir(path)
  399. def unlinkpath(f, ignoremissing=False):
  400. """unlink and remove the directory if it is empty"""
  401. try:
  402. os.unlink(f)
  403. except OSError, e:
  404. if not (ignoremissing and e.errno == errno.ENOENT):
  405. raise
  406. # try removing directories that might now be empty
  407. try:
  408. os.removedirs(os.path.dirname(f))
  409. except OSError:
  410. pass
  411. def lookupreg(key, name=None, scope=None):
  412. return None
  413. def hidewindow():
  414. """Hide current shell window.
  415. Used to hide the window opened when starting asynchronous
  416. child process under Windows, unneeded on other systems.
  417. """
  418. pass
  419. class cachestat(object):
  420. def __init__(self, path):
  421. self.stat = os.stat(path)
  422. def cacheable(self):
  423. return bool(self.stat.st_ino)
  424. __hash__ = object.__hash__
  425. def __eq__(self, other):
  426. try:
  427. # Only dev, ino, size, mtime and atime are likely to change. Out
  428. # of these, we shouldn't compare atime but should compare the
  429. # rest. However, one of the other fields changing indicates
  430. # something fishy going on, so return False if anything but atime
  431. # changes.
  432. return (self.stat.st_mode == other.stat.st_mode and
  433. self.stat.st_ino == other.stat.st_ino and
  434. self.stat.st_dev == other.stat.st_dev and
  435. self.stat.st_nlink == other.stat.st_nlink and
  436. self.stat.st_uid == other.stat.st_uid and
  437. self.stat.st_gid == other.stat.st_gid and
  438. self.stat.st_size == other.stat.st_size and
  439. self.stat.st_mtime == other.stat.st_mtime and
  440. self.stat.st_ctime == other.stat.st_ctime)
  441. except AttributeError:
  442. return False
  443. def __ne__(self, other):
  444. return not self == other
  445. def executablepath():
  446. return None # available on Windows only
  447. class unixdomainserver(socket.socket):
  448. def __init__(self, join, subsystem):
  449. '''Create a unix domain socket with the given prefix.'''
  450. super(unixdomainserver, self).__init__(socket.AF_UNIX)
  451. sockname = subsystem + '.sock'
  452. self.realpath = self.path = join(sockname)
  453. if os.path.islink(self.path):
  454. if os.path.exists(self.path):
  455. self.realpath = os.readlink(self.path)
  456. else:
  457. os.unlink(self.path)
  458. try:
  459. self.bind(self.realpath)
  460. except socket.error, err:
  461. if err.args[0] == 'AF_UNIX path too long':
  462. tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
  463. self.realpath = os.path.join(tmpdir, sockname)
  464. try:
  465. self.bind(self.realpath)
  466. os.symlink(self.realpath, self.path)
  467. except (OSError, socket.error):
  468. self.cleanup()
  469. raise
  470. else:
  471. raise
  472. self.listen(5)
  473. def cleanup(self):
  474. def okayifmissing(f, path):
  475. try:
  476. f(path)
  477. except OSError, err:
  478. if err.errno != errno.ENOENT:
  479. raise
  480. okayifmissing(os.unlink, self.path)
  481. if self.realpath != self.path:
  482. okayifmissing(os.unlink, self.realpath)
  483. okayifmissing(os.rmdir, os.path.dirname(self.realpath))
  484. def statislink(st):
  485. '''check whether a stat result is a symlink'''
  486. return st and stat.S_ISLNK(st.st_mode)
  487. def statisexec(st):
  488. '''check whether a stat result is an executable file'''
  489. return st and (st.st_mode & 0100 != 0)