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