##// END OF EJS Templates
py3: make posix.getuser return a bytes
Pulkit Goyal -
r32129:f0ea0f64 default
parent child Browse files
Show More
@@ -1,649 +1,649
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 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 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 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 unlink(checknoexec)
191 191 # checkisexec exists but is not exec - delete it
192 192 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 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 fscheckdir = pycompat.fsdecode(checkdir)
234 234 name = tempfile.mktemp(dir=fscheckdir,
235 235 prefix=r'checklink-')
236 236 name = pycompat.fsencode(name)
237 237 try:
238 238 fd = None
239 239 if cachedir is None:
240 240 fd = tempfile.NamedTemporaryFile(dir=fscheckdir,
241 241 prefix=r'hg-checklink-')
242 242 target = pycompat.fsencode(os.path.basename(fd.name))
243 243 else:
244 244 # create a fixed file to link to; doesn't matter if it
245 245 # already exists.
246 246 target = 'checklink-target'
247 247 open(os.path.join(cachedir, target), 'w').close()
248 248 try:
249 249 os.symlink(target, name)
250 250 if cachedir is None:
251 251 unlink(name)
252 252 else:
253 253 try:
254 254 os.rename(name, checklink)
255 255 except OSError:
256 256 unlink(name)
257 257 return True
258 258 except OSError as inst:
259 259 # link creation might race, try again
260 260 if inst[0] == errno.EEXIST:
261 261 continue
262 262 raise
263 263 finally:
264 264 if fd is not None:
265 265 fd.close()
266 266 except AttributeError:
267 267 return False
268 268 except OSError as inst:
269 269 # sshfs might report failure while successfully creating the link
270 270 if inst[0] == errno.EIO and os.path.exists(name):
271 271 unlink(name)
272 272 return False
273 273
274 274 def checkosfilename(path):
275 275 '''Check that the base-relative path is a valid filename on this platform.
276 276 Returns None if the path is ok, or a UI string describing the problem.'''
277 277 pass # on posix platforms, every path is ok
278 278
279 279 def setbinary(fd):
280 280 pass
281 281
282 282 def pconvert(path):
283 283 return path
284 284
285 285 def localpath(path):
286 286 return path
287 287
288 288 def samefile(fpath1, fpath2):
289 289 """Returns whether path1 and path2 refer to the same file. This is only
290 290 guaranteed to work for files, not directories."""
291 291 return os.path.samefile(fpath1, fpath2)
292 292
293 293 def samedevice(fpath1, fpath2):
294 294 """Returns whether fpath1 and fpath2 are on the same device. This is only
295 295 guaranteed to work for files, not directories."""
296 296 st1 = os.lstat(fpath1)
297 297 st2 = os.lstat(fpath2)
298 298 return st1.st_dev == st2.st_dev
299 299
300 300 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
301 301 def normcase(path):
302 302 return path.lower()
303 303
304 304 # what normcase does to ASCII strings
305 305 normcasespec = encoding.normcasespecs.lower
306 306 # fallback normcase function for non-ASCII strings
307 307 normcasefallback = normcase
308 308
309 309 if pycompat.sysplatform == 'darwin':
310 310
311 311 def normcase(path):
312 312 '''
313 313 Normalize a filename for OS X-compatible comparison:
314 314 - escape-encode invalid characters
315 315 - decompose to NFD
316 316 - lowercase
317 317 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
318 318
319 319 >>> normcase('UPPER')
320 320 'upper'
321 321 >>> normcase('Caf\xc3\xa9')
322 322 'cafe\\xcc\\x81'
323 323 >>> normcase('\xc3\x89')
324 324 'e\\xcc\\x81'
325 325 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
326 326 '%b8%ca%c3\\xca\\xbe%c8.jpg'
327 327 '''
328 328
329 329 try:
330 330 return encoding.asciilower(path) # exception for non-ASCII
331 331 except UnicodeDecodeError:
332 332 return normcasefallback(path)
333 333
334 334 normcasespec = encoding.normcasespecs.lower
335 335
336 336 def normcasefallback(path):
337 337 try:
338 338 u = path.decode('utf-8')
339 339 except UnicodeDecodeError:
340 340 # OS X percent-encodes any bytes that aren't valid utf-8
341 341 s = ''
342 342 pos = 0
343 343 l = len(path)
344 344 while pos < l:
345 345 try:
346 346 c = encoding.getutf8char(path, pos)
347 347 pos += len(c)
348 348 except ValueError:
349 349 c = '%%%02X' % ord(path[pos])
350 350 pos += 1
351 351 s += c
352 352
353 353 u = s.decode('utf-8')
354 354
355 355 # Decompose then lowercase (HFS+ technote specifies lower)
356 356 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
357 357 # drop HFS+ ignored characters
358 358 return encoding.hfsignoreclean(enc)
359 359
360 360 if pycompat.sysplatform == 'cygwin':
361 361 # workaround for cygwin, in which mount point part of path is
362 362 # treated as case sensitive, even though underlying NTFS is case
363 363 # insensitive.
364 364
365 365 # default mount points
366 366 cygwinmountpoints = sorted([
367 367 "/usr/bin",
368 368 "/usr/lib",
369 369 "/cygdrive",
370 370 ], reverse=True)
371 371
372 372 # use upper-ing as normcase as same as NTFS workaround
373 373 def normcase(path):
374 374 pathlen = len(path)
375 375 if (pathlen == 0) or (path[0] != pycompat.ossep):
376 376 # treat as relative
377 377 return encoding.upper(path)
378 378
379 379 # to preserve case of mountpoint part
380 380 for mp in cygwinmountpoints:
381 381 if not path.startswith(mp):
382 382 continue
383 383
384 384 mplen = len(mp)
385 385 if mplen == pathlen: # mount point itself
386 386 return mp
387 387 if path[mplen] == pycompat.ossep:
388 388 return mp + encoding.upper(path[mplen:])
389 389
390 390 return encoding.upper(path)
391 391
392 392 normcasespec = encoding.normcasespecs.other
393 393 normcasefallback = normcase
394 394
395 395 # Cygwin translates native ACLs to POSIX permissions,
396 396 # but these translations are not supported by native
397 397 # tools, so the exec bit tends to be set erroneously.
398 398 # Therefore, disable executable bit access on Cygwin.
399 399 def checkexec(path):
400 400 return False
401 401
402 402 # Similarly, Cygwin's symlink emulation is likely to create
403 403 # problems when Mercurial is used from both Cygwin and native
404 404 # Windows, with other native tools, or on shared volumes
405 405 def checklink(path):
406 406 return False
407 407
408 408 _needsshellquote = None
409 409 def shellquote(s):
410 410 if pycompat.sysplatform == 'OpenVMS':
411 411 return '"%s"' % s
412 412 global _needsshellquote
413 413 if _needsshellquote is None:
414 414 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
415 415 if s and not _needsshellquote(s):
416 416 # "s" shouldn't have to be quoted
417 417 return s
418 418 else:
419 419 return "'%s'" % s.replace("'", "'\\''")
420 420
421 421 def quotecommand(cmd):
422 422 return cmd
423 423
424 424 def popen(command, mode='r'):
425 425 return os.popen(command, mode)
426 426
427 427 def testpid(pid):
428 428 '''return False if pid dead, True if running or not sure'''
429 429 if pycompat.sysplatform == 'OpenVMS':
430 430 return True
431 431 try:
432 432 os.kill(pid, 0)
433 433 return True
434 434 except OSError as inst:
435 435 return inst.errno != errno.ESRCH
436 436
437 437 def explainexit(code):
438 438 """return a 2-tuple (desc, code) describing a subprocess status
439 439 (codes from kill are negative - not os.system/wait encoding)"""
440 440 if code >= 0:
441 441 return _("exited with status %d") % code, code
442 442 return _("killed by signal %d") % -code, -code
443 443
444 444 def isowner(st):
445 445 """Return True if the stat object st is from the current user."""
446 446 return st.st_uid == os.getuid()
447 447
448 448 def findexe(command):
449 449 '''Find executable for command searching like which does.
450 450 If command is a basename then PATH is searched for command.
451 451 PATH isn't searched if command is an absolute or relative path.
452 452 If command isn't found None is returned.'''
453 453 if pycompat.sysplatform == 'OpenVMS':
454 454 return command
455 455
456 456 def findexisting(executable):
457 457 'Will return executable if existing file'
458 458 if os.path.isfile(executable) and os.access(executable, os.X_OK):
459 459 return executable
460 460 return None
461 461
462 462 if pycompat.ossep in command:
463 463 return findexisting(command)
464 464
465 465 if pycompat.sysplatform == 'plan9':
466 466 return findexisting(os.path.join('/bin', command))
467 467
468 468 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
469 469 executable = findexisting(os.path.join(path, command))
470 470 if executable is not None:
471 471 return executable
472 472 return None
473 473
474 474 def setsignalhandler():
475 475 pass
476 476
477 477 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
478 478
479 479 def statfiles(files):
480 480 '''Stat each file in files. Yield each stat, or None if a file does not
481 481 exist or has a type we don't care about.'''
482 482 lstat = os.lstat
483 483 getkind = stat.S_IFMT
484 484 for nf in files:
485 485 try:
486 486 st = lstat(nf)
487 487 if getkind(st.st_mode) not in _wantedkinds:
488 488 st = None
489 489 except OSError as err:
490 490 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
491 491 raise
492 492 st = None
493 493 yield st
494 494
495 495 def getuser():
496 496 '''return name of current user'''
497 return getpass.getuser()
497 return pycompat.fsencode(getpass.getuser())
498 498
499 499 def username(uid=None):
500 500 """Return the name of the user with the given uid.
501 501
502 502 If uid is None, return the name of the current user."""
503 503
504 504 if uid is None:
505 505 uid = os.getuid()
506 506 try:
507 507 return pwd.getpwuid(uid)[0]
508 508 except KeyError:
509 509 return str(uid)
510 510
511 511 def groupname(gid=None):
512 512 """Return the name of the group with the given gid.
513 513
514 514 If gid is None, return the name of the current group."""
515 515
516 516 if gid is None:
517 517 gid = os.getgid()
518 518 try:
519 519 return grp.getgrgid(gid)[0]
520 520 except KeyError:
521 521 return str(gid)
522 522
523 523 def groupmembers(name):
524 524 """Return the list of members of the group with the given
525 525 name, KeyError if the group does not exist.
526 526 """
527 527 return list(grp.getgrnam(name).gr_mem)
528 528
529 529 def spawndetached(args):
530 530 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
531 531 args[0], args)
532 532
533 533 def gethgcmd():
534 534 return sys.argv[:1]
535 535
536 536 def makedir(path, notindexed):
537 537 os.mkdir(path)
538 538
539 539 def lookupreg(key, name=None, scope=None):
540 540 return None
541 541
542 542 def hidewindow():
543 543 """Hide current shell window.
544 544
545 545 Used to hide the window opened when starting asynchronous
546 546 child process under Windows, unneeded on other systems.
547 547 """
548 548 pass
549 549
550 550 class cachestat(object):
551 551 def __init__(self, path):
552 552 self.stat = os.stat(path)
553 553
554 554 def cacheable(self):
555 555 return bool(self.stat.st_ino)
556 556
557 557 __hash__ = object.__hash__
558 558
559 559 def __eq__(self, other):
560 560 try:
561 561 # Only dev, ino, size, mtime and atime are likely to change. Out
562 562 # of these, we shouldn't compare atime but should compare the
563 563 # rest. However, one of the other fields changing indicates
564 564 # something fishy going on, so return False if anything but atime
565 565 # changes.
566 566 return (self.stat.st_mode == other.stat.st_mode and
567 567 self.stat.st_ino == other.stat.st_ino and
568 568 self.stat.st_dev == other.stat.st_dev and
569 569 self.stat.st_nlink == other.stat.st_nlink and
570 570 self.stat.st_uid == other.stat.st_uid and
571 571 self.stat.st_gid == other.stat.st_gid and
572 572 self.stat.st_size == other.stat.st_size and
573 573 self.stat.st_mtime == other.stat.st_mtime and
574 574 self.stat.st_ctime == other.stat.st_ctime)
575 575 except AttributeError:
576 576 return False
577 577
578 578 def __ne__(self, other):
579 579 return not self == other
580 580
581 581 def executablepath():
582 582 return None # available on Windows only
583 583
584 584 def statislink(st):
585 585 '''check whether a stat result is a symlink'''
586 586 return st and stat.S_ISLNK(st.st_mode)
587 587
588 588 def statisexec(st):
589 589 '''check whether a stat result is an executable file'''
590 590 return st and (st.st_mode & 0o100 != 0)
591 591
592 592 def poll(fds):
593 593 """block until something happens on any file descriptor
594 594
595 595 This is a generic helper that will check for any activity
596 596 (read, write. exception) and return the list of touched files.
597 597
598 598 In unsupported cases, it will raise a NotImplementedError"""
599 599 try:
600 600 while True:
601 601 try:
602 602 res = select.select(fds, fds, fds)
603 603 break
604 604 except select.error as inst:
605 605 if inst.args[0] == errno.EINTR:
606 606 continue
607 607 raise
608 608 except ValueError: # out of range file descriptor
609 609 raise NotImplementedError()
610 610 return sorted(list(set(sum(res, []))))
611 611
612 612 def readpipe(pipe):
613 613 """Read all available data from a pipe."""
614 614 # We can't fstat() a pipe because Linux will always report 0.
615 615 # So, we set the pipe to non-blocking mode and read everything
616 616 # that's available.
617 617 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
618 618 flags |= os.O_NONBLOCK
619 619 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
620 620
621 621 try:
622 622 chunks = []
623 623 while True:
624 624 try:
625 625 s = pipe.read()
626 626 if not s:
627 627 break
628 628 chunks.append(s)
629 629 except IOError:
630 630 break
631 631
632 632 return ''.join(chunks)
633 633 finally:
634 634 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
635 635
636 636 def bindunixsocket(sock, path):
637 637 """Bind the UNIX domain socket to the specified path"""
638 638 # use relative path instead of full path at bind() if possible, since
639 639 # AF_UNIX path has very small length limit (107 chars) on common
640 640 # platforms (see sys/un.h)
641 641 dirname, basename = os.path.split(path)
642 642 bakwdfd = None
643 643 if dirname:
644 644 bakwdfd = os.open('.', os.O_DIRECTORY)
645 645 os.chdir(dirname)
646 646 sock.bind(basename)
647 647 if bakwdfd:
648 648 os.fchdir(bakwdfd)
649 649 os.close(bakwdfd)
General Comments 0
You need to be logged in to leave comments. Login now