##// END OF EJS Templates
posix: simplify checkexec check...
Mads Kiilerich -
r30445:1ce4c206 default
parent child Browse files
Show More
@@ -1,588 +1,588 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import fcntl
12 12 import getpass
13 13 import grp
14 14 import os
15 15 import pwd
16 16 import re
17 17 import select
18 18 import stat
19 19 import sys
20 20 import tempfile
21 21 import unicodedata
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 encoding,
26 26 )
27 27
28 28 posixfile = open
29 29 normpath = os.path.normpath
30 30 samestat = os.path.samestat
31 31 try:
32 32 oslink = os.link
33 33 except AttributeError:
34 34 # Some platforms build Python without os.link on systems that are
35 35 # vaguely unix-like but don't have hardlink support. For those
36 36 # poor souls, just say we tried and that it failed so we fall back
37 37 # to copies.
38 38 def oslink(src, dst):
39 39 raise OSError(errno.EINVAL,
40 40 'hardlinks not supported: %s to %s' % (src, dst))
41 41 unlink = os.unlink
42 42 rename = os.rename
43 43 removedirs = os.removedirs
44 44 expandglobs = False
45 45
46 46 umask = os.umask(0)
47 47 os.umask(umask)
48 48
49 49 def split(p):
50 50 '''Same as posixpath.split, but faster
51 51
52 52 >>> import posixpath
53 53 >>> for f in ['/absolute/path/to/file',
54 54 ... 'relative/path/to/file',
55 55 ... 'file_alone',
56 56 ... 'path/to/directory/',
57 57 ... '/multiple/path//separators',
58 58 ... '/file_at_root',
59 59 ... '///multiple_leading_separators_at_root',
60 60 ... '']:
61 61 ... assert split(f) == posixpath.split(f), f
62 62 '''
63 63 ht = p.rsplit('/', 1)
64 64 if len(ht) == 1:
65 65 return '', p
66 66 nh = ht[0].rstrip('/')
67 67 if nh:
68 68 return nh, ht[1]
69 69 return ht[0] + '/', ht[1]
70 70
71 71 def openhardlinks():
72 72 '''return true if it is safe to hold open file handles to hardlinks'''
73 73 return True
74 74
75 75 def nlinks(name):
76 76 '''return number of hardlinks for the given file'''
77 77 return os.lstat(name).st_nlink
78 78
79 79 def parsepatchoutput(output_line):
80 80 """parses the output produced by patch and returns the filename"""
81 81 pf = output_line[14:]
82 82 if os.sys.platform == 'OpenVMS':
83 83 if pf[0] == '`':
84 84 pf = pf[1:-1] # Remove the quotes
85 85 else:
86 86 if pf.startswith("'") and pf.endswith("'") and " " in pf:
87 87 pf = pf[1:-1] # Remove the quotes
88 88 return pf
89 89
90 90 def sshargs(sshcmd, host, user, port):
91 91 '''Build argument list for ssh'''
92 92 args = user and ("%s@%s" % (user, host)) or host
93 93 return port and ("%s -p %s" % (args, port)) or args
94 94
95 95 def isexec(f):
96 96 """check whether a file is executable"""
97 97 return (os.lstat(f).st_mode & 0o100 != 0)
98 98
99 99 def setflags(f, l, x):
100 100 s = os.lstat(f).st_mode
101 101 if l:
102 102 if not stat.S_ISLNK(s):
103 103 # switch file to link
104 104 fp = open(f)
105 105 data = fp.read()
106 106 fp.close()
107 107 os.unlink(f)
108 108 try:
109 109 os.symlink(data, f)
110 110 except OSError:
111 111 # failed to make a link, rewrite file
112 112 fp = open(f, "w")
113 113 fp.write(data)
114 114 fp.close()
115 115 # no chmod needed at this point
116 116 return
117 117 if stat.S_ISLNK(s):
118 118 # switch link to file
119 119 data = os.readlink(f)
120 120 os.unlink(f)
121 121 fp = open(f, "w")
122 122 fp.write(data)
123 123 fp.close()
124 124 s = 0o666 & ~umask # avoid restatting for chmod
125 125
126 126 sx = s & 0o100
127 127 if x and not sx:
128 128 # Turn on +x for every +r bit when making a file executable
129 129 # and obey umask.
130 130 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
131 131 elif not x and sx:
132 132 # Turn off all +x bits
133 133 os.chmod(f, s & 0o666)
134 134
135 135 def copymode(src, dst, mode=None):
136 136 '''Copy the file mode from the file at path src to dst.
137 137 If src doesn't exist, we're using mode instead. If mode is None, we're
138 138 using umask.'''
139 139 try:
140 140 st_mode = os.lstat(src).st_mode & 0o777
141 141 except OSError as inst:
142 142 if inst.errno != errno.ENOENT:
143 143 raise
144 144 st_mode = mode
145 145 if st_mode is None:
146 146 st_mode = ~umask
147 147 st_mode &= 0o666
148 148 os.chmod(dst, st_mode)
149 149
150 150 def checkexec(path):
151 151 """
152 152 Check whether the given path is on a filesystem with UNIX-like exec flags
153 153
154 154 Requires a directory (like /foo/.hg)
155 155 """
156 156
157 157 # VFAT on some Linux versions can flip mode but it doesn't persist
158 158 # a FS remount. Frequently we can detect it if files are created
159 159 # with exec bit on.
160 160
161 161 try:
162 162 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
163 163 cachedir = os.path.join(path, '.hg', 'cache')
164 164 if not os.path.isdir(cachedir):
165 165 cachedir = path
166 166 fh, fn = tempfile.mkstemp(dir=cachedir, prefix='hg-checkexec-')
167 167 try:
168 168 os.close(fh)
169 m = os.stat(fn).st_mode & 0o777
170 new_file_has_exec = m & EXECFLAGS
171 os.chmod(fn, m ^ EXECFLAGS)
172 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
169 m = os.stat(fn).st_mode
170 if m & EXECFLAGS:
171 return False
172 os.chmod(fn, m & 0o777 | EXECFLAGS)
173 return os.stat(fn).st_mode & EXECFLAGS
173 174 finally:
174 175 os.unlink(fn)
175 176 except (IOError, OSError):
176 177 # we don't care, the user probably won't be able to commit anyway
177 178 return False
178 return not (new_file_has_exec or exec_flags_cannot_flip)
179 179
180 180 def checklink(path):
181 181 """check whether the given path is on a symlink-capable filesystem"""
182 182 # mktemp is not racy because symlink creation will fail if the
183 183 # file already exists
184 184 while True:
185 185 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
186 186 try:
187 187 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
188 188 try:
189 189 os.symlink(os.path.basename(fd.name), name)
190 190 os.unlink(name)
191 191 return True
192 192 except OSError as inst:
193 193 # link creation might race, try again
194 194 if inst[0] == errno.EEXIST:
195 195 continue
196 196 raise
197 197 finally:
198 198 fd.close()
199 199 except AttributeError:
200 200 return False
201 201 except OSError as inst:
202 202 # sshfs might report failure while successfully creating the link
203 203 if inst[0] == errno.EIO and os.path.exists(name):
204 204 os.unlink(name)
205 205 return False
206 206
207 207 def checkosfilename(path):
208 208 '''Check that the base-relative path is a valid filename on this platform.
209 209 Returns None if the path is ok, or a UI string describing the problem.'''
210 210 pass # on posix platforms, every path is ok
211 211
212 212 def setbinary(fd):
213 213 pass
214 214
215 215 def pconvert(path):
216 216 return path
217 217
218 218 def localpath(path):
219 219 return path
220 220
221 221 def samefile(fpath1, fpath2):
222 222 """Returns whether path1 and path2 refer to the same file. This is only
223 223 guaranteed to work for files, not directories."""
224 224 return os.path.samefile(fpath1, fpath2)
225 225
226 226 def samedevice(fpath1, fpath2):
227 227 """Returns whether fpath1 and fpath2 are on the same device. This is only
228 228 guaranteed to work for files, not directories."""
229 229 st1 = os.lstat(fpath1)
230 230 st2 = os.lstat(fpath2)
231 231 return st1.st_dev == st2.st_dev
232 232
233 233 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
234 234 def normcase(path):
235 235 return path.lower()
236 236
237 237 # what normcase does to ASCII strings
238 238 normcasespec = encoding.normcasespecs.lower
239 239 # fallback normcase function for non-ASCII strings
240 240 normcasefallback = normcase
241 241
242 242 if sys.platform == 'darwin':
243 243
244 244 def normcase(path):
245 245 '''
246 246 Normalize a filename for OS X-compatible comparison:
247 247 - escape-encode invalid characters
248 248 - decompose to NFD
249 249 - lowercase
250 250 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
251 251
252 252 >>> normcase('UPPER')
253 253 'upper'
254 254 >>> normcase('Caf\xc3\xa9')
255 255 'cafe\\xcc\\x81'
256 256 >>> normcase('\xc3\x89')
257 257 'e\\xcc\\x81'
258 258 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
259 259 '%b8%ca%c3\\xca\\xbe%c8.jpg'
260 260 '''
261 261
262 262 try:
263 263 return encoding.asciilower(path) # exception for non-ASCII
264 264 except UnicodeDecodeError:
265 265 return normcasefallback(path)
266 266
267 267 normcasespec = encoding.normcasespecs.lower
268 268
269 269 def normcasefallback(path):
270 270 try:
271 271 u = path.decode('utf-8')
272 272 except UnicodeDecodeError:
273 273 # OS X percent-encodes any bytes that aren't valid utf-8
274 274 s = ''
275 275 pos = 0
276 276 l = len(path)
277 277 while pos < l:
278 278 try:
279 279 c = encoding.getutf8char(path, pos)
280 280 pos += len(c)
281 281 except ValueError:
282 282 c = '%%%02X' % ord(path[pos])
283 283 pos += 1
284 284 s += c
285 285
286 286 u = s.decode('utf-8')
287 287
288 288 # Decompose then lowercase (HFS+ technote specifies lower)
289 289 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
290 290 # drop HFS+ ignored characters
291 291 return encoding.hfsignoreclean(enc)
292 292
293 293 if sys.platform == 'cygwin':
294 294 # workaround for cygwin, in which mount point part of path is
295 295 # treated as case sensitive, even though underlying NTFS is case
296 296 # insensitive.
297 297
298 298 # default mount points
299 299 cygwinmountpoints = sorted([
300 300 "/usr/bin",
301 301 "/usr/lib",
302 302 "/cygdrive",
303 303 ], reverse=True)
304 304
305 305 # use upper-ing as normcase as same as NTFS workaround
306 306 def normcase(path):
307 307 pathlen = len(path)
308 308 if (pathlen == 0) or (path[0] != os.sep):
309 309 # treat as relative
310 310 return encoding.upper(path)
311 311
312 312 # to preserve case of mountpoint part
313 313 for mp in cygwinmountpoints:
314 314 if not path.startswith(mp):
315 315 continue
316 316
317 317 mplen = len(mp)
318 318 if mplen == pathlen: # mount point itself
319 319 return mp
320 320 if path[mplen] == os.sep:
321 321 return mp + encoding.upper(path[mplen:])
322 322
323 323 return encoding.upper(path)
324 324
325 325 normcasespec = encoding.normcasespecs.other
326 326 normcasefallback = normcase
327 327
328 328 # Cygwin translates native ACLs to POSIX permissions,
329 329 # but these translations are not supported by native
330 330 # tools, so the exec bit tends to be set erroneously.
331 331 # Therefore, disable executable bit access on Cygwin.
332 332 def checkexec(path):
333 333 return False
334 334
335 335 # Similarly, Cygwin's symlink emulation is likely to create
336 336 # problems when Mercurial is used from both Cygwin and native
337 337 # Windows, with other native tools, or on shared volumes
338 338 def checklink(path):
339 339 return False
340 340
341 341 _needsshellquote = None
342 342 def shellquote(s):
343 343 if os.sys.platform == 'OpenVMS':
344 344 return '"%s"' % s
345 345 global _needsshellquote
346 346 if _needsshellquote is None:
347 347 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/+-]').search
348 348 if s and not _needsshellquote(s):
349 349 # "s" shouldn't have to be quoted
350 350 return s
351 351 else:
352 352 return "'%s'" % s.replace("'", "'\\''")
353 353
354 354 def quotecommand(cmd):
355 355 return cmd
356 356
357 357 def popen(command, mode='r'):
358 358 return os.popen(command, mode)
359 359
360 360 def testpid(pid):
361 361 '''return False if pid dead, True if running or not sure'''
362 362 if os.sys.platform == 'OpenVMS':
363 363 return True
364 364 try:
365 365 os.kill(pid, 0)
366 366 return True
367 367 except OSError as inst:
368 368 return inst.errno != errno.ESRCH
369 369
370 370 def explainexit(code):
371 371 """return a 2-tuple (desc, code) describing a subprocess status
372 372 (codes from kill are negative - not os.system/wait encoding)"""
373 373 if code >= 0:
374 374 return _("exited with status %d") % code, code
375 375 return _("killed by signal %d") % -code, -code
376 376
377 377 def isowner(st):
378 378 """Return True if the stat object st is from the current user."""
379 379 return st.st_uid == os.getuid()
380 380
381 381 def findexe(command):
382 382 '''Find executable for command searching like which does.
383 383 If command is a basename then PATH is searched for command.
384 384 PATH isn't searched if command is an absolute or relative path.
385 385 If command isn't found None is returned.'''
386 386 if sys.platform == 'OpenVMS':
387 387 return command
388 388
389 389 def findexisting(executable):
390 390 'Will return executable if existing file'
391 391 if os.path.isfile(executable) and os.access(executable, os.X_OK):
392 392 return executable
393 393 return None
394 394
395 395 if os.sep in command:
396 396 return findexisting(command)
397 397
398 398 if sys.platform == 'plan9':
399 399 return findexisting(os.path.join('/bin', command))
400 400
401 401 for path in os.environ.get('PATH', '').split(os.pathsep):
402 402 executable = findexisting(os.path.join(path, command))
403 403 if executable is not None:
404 404 return executable
405 405 return None
406 406
407 407 def setsignalhandler():
408 408 pass
409 409
410 410 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
411 411
412 412 def statfiles(files):
413 413 '''Stat each file in files. Yield each stat, or None if a file does not
414 414 exist or has a type we don't care about.'''
415 415 lstat = os.lstat
416 416 getkind = stat.S_IFMT
417 417 for nf in files:
418 418 try:
419 419 st = lstat(nf)
420 420 if getkind(st.st_mode) not in _wantedkinds:
421 421 st = None
422 422 except OSError as err:
423 423 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
424 424 raise
425 425 st = None
426 426 yield st
427 427
428 428 def getuser():
429 429 '''return name of current user'''
430 430 return getpass.getuser()
431 431
432 432 def username(uid=None):
433 433 """Return the name of the user with the given uid.
434 434
435 435 If uid is None, return the name of the current user."""
436 436
437 437 if uid is None:
438 438 uid = os.getuid()
439 439 try:
440 440 return pwd.getpwuid(uid)[0]
441 441 except KeyError:
442 442 return str(uid)
443 443
444 444 def groupname(gid=None):
445 445 """Return the name of the group with the given gid.
446 446
447 447 If gid is None, return the name of the current group."""
448 448
449 449 if gid is None:
450 450 gid = os.getgid()
451 451 try:
452 452 return grp.getgrgid(gid)[0]
453 453 except KeyError:
454 454 return str(gid)
455 455
456 456 def groupmembers(name):
457 457 """Return the list of members of the group with the given
458 458 name, KeyError if the group does not exist.
459 459 """
460 460 return list(grp.getgrnam(name).gr_mem)
461 461
462 462 def spawndetached(args):
463 463 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
464 464 args[0], args)
465 465
466 466 def gethgcmd():
467 467 return sys.argv[:1]
468 468
469 469 def makedir(path, notindexed):
470 470 os.mkdir(path)
471 471
472 472 def unlinkpath(f, ignoremissing=False):
473 473 """unlink and remove the directory if it is empty"""
474 474 try:
475 475 os.unlink(f)
476 476 except OSError as e:
477 477 if not (ignoremissing and e.errno == errno.ENOENT):
478 478 raise
479 479 # try removing directories that might now be empty
480 480 try:
481 481 os.removedirs(os.path.dirname(f))
482 482 except OSError:
483 483 pass
484 484
485 485 def lookupreg(key, name=None, scope=None):
486 486 return None
487 487
488 488 def hidewindow():
489 489 """Hide current shell window.
490 490
491 491 Used to hide the window opened when starting asynchronous
492 492 child process under Windows, unneeded on other systems.
493 493 """
494 494 pass
495 495
496 496 class cachestat(object):
497 497 def __init__(self, path):
498 498 self.stat = os.stat(path)
499 499
500 500 def cacheable(self):
501 501 return bool(self.stat.st_ino)
502 502
503 503 __hash__ = object.__hash__
504 504
505 505 def __eq__(self, other):
506 506 try:
507 507 # Only dev, ino, size, mtime and atime are likely to change. Out
508 508 # of these, we shouldn't compare atime but should compare the
509 509 # rest. However, one of the other fields changing indicates
510 510 # something fishy going on, so return False if anything but atime
511 511 # changes.
512 512 return (self.stat.st_mode == other.stat.st_mode and
513 513 self.stat.st_ino == other.stat.st_ino and
514 514 self.stat.st_dev == other.stat.st_dev and
515 515 self.stat.st_nlink == other.stat.st_nlink and
516 516 self.stat.st_uid == other.stat.st_uid and
517 517 self.stat.st_gid == other.stat.st_gid and
518 518 self.stat.st_size == other.stat.st_size and
519 519 self.stat.st_mtime == other.stat.st_mtime and
520 520 self.stat.st_ctime == other.stat.st_ctime)
521 521 except AttributeError:
522 522 return False
523 523
524 524 def __ne__(self, other):
525 525 return not self == other
526 526
527 527 def executablepath():
528 528 return None # available on Windows only
529 529
530 530 def statislink(st):
531 531 '''check whether a stat result is a symlink'''
532 532 return st and stat.S_ISLNK(st.st_mode)
533 533
534 534 def statisexec(st):
535 535 '''check whether a stat result is an executable file'''
536 536 return st and (st.st_mode & 0o100 != 0)
537 537
538 538 def poll(fds):
539 539 """block until something happens on any file descriptor
540 540
541 541 This is a generic helper that will check for any activity
542 542 (read, write. exception) and return the list of touched files.
543 543
544 544 In unsupported cases, it will raise a NotImplementedError"""
545 545 try:
546 546 res = select.select(fds, fds, fds)
547 547 except ValueError: # out of range file descriptor
548 548 raise NotImplementedError()
549 549 return sorted(list(set(sum(res, []))))
550 550
551 551 def readpipe(pipe):
552 552 """Read all available data from a pipe."""
553 553 # We can't fstat() a pipe because Linux will always report 0.
554 554 # So, we set the pipe to non-blocking mode and read everything
555 555 # that's available.
556 556 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
557 557 flags |= os.O_NONBLOCK
558 558 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
559 559
560 560 try:
561 561 chunks = []
562 562 while True:
563 563 try:
564 564 s = pipe.read()
565 565 if not s:
566 566 break
567 567 chunks.append(s)
568 568 except IOError:
569 569 break
570 570
571 571 return ''.join(chunks)
572 572 finally:
573 573 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
574 574
575 575 def bindunixsocket(sock, path):
576 576 """Bind the UNIX domain socket to the specified path"""
577 577 # use relative path instead of full path at bind() if possible, since
578 578 # AF_UNIX path has very small length limit (107 chars) on common
579 579 # platforms (see sys/un.h)
580 580 dirname, basename = os.path.split(path)
581 581 bakwdfd = None
582 582 if dirname:
583 583 bakwdfd = os.open('.', os.O_DIRECTORY)
584 584 os.chdir(dirname)
585 585 sock.bind(basename)
586 586 if bakwdfd:
587 587 os.fchdir(bakwdfd)
588 588 os.close(bakwdfd)
General Comments 0
You need to be logged in to leave comments. Login now