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