72
# Cross platform wall-clock time functionality with decent resolution.
73
# On Linux ``time.clock`` returns only CPU time. On Windows, ``time.time()``
74
# only has a resolution of ~15ms. Note that ``time.clock()`` is not
75
# synchronized with ``time.time()``, this is only meant to be used to find
76
# delta times by subtracting from another call to this function.
77
timer_func = time.time
78
if sys.platform == 'win32':
79
timer_func = time.clock
66
81
# On win32, O_BINARY is used to indicate the file should
67
82
# be opened in binary mode, rather than text mode.
74
89
O_NOINHERIT = getattr(os, 'O_NOINHERIT', 0)
77
class UnsupportedTimezoneFormat(errors.BzrError):
79
_fmt = ('Unsupported timezone format "%(timezone)s", '
80
'options are "utc", "original", "local".')
82
def __init__(self, timezone):
83
self.timezone = timezone
92
def get_unicode_argv():
96
user_encoding = get_user_encoding()
97
return [a.decode(user_encoding) for a in sys.argv[1:]]
98
except UnicodeDecodeError:
99
raise errors.BzrError(gettext("Parameter {0!r} encoding is unsupported by {1} "
100
"application locale.").format(a, user_encoding))
86
103
def make_readonly(filename):
202
215
except OSError as e:
203
216
if e.errno == errno.ENOENT:
206
raise errors.BzrError(
207
gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
219
raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
210
222
def fancy_rename(old, new, rename_func, unlink_func):
260
272
# case-insensitive filesystem), so we may have accidentally renamed
261
273
# source by when we tried to rename target
262
274
if (file_existed and e.errno in (None, errno.ENOENT)
263
and old.lower() == new.lower()):
275
and old.lower() == new.lower()):
264
276
# source and target are the same file on a case-insensitive
265
277
# filesystem, so we don't generate an exception
301
313
# as a special case here by simply removing the first slash, as we consider
302
314
# that breaking POSIX compatibility for this obscure feature is acceptable.
303
315
# This is not a paranoid precaution, as we notably get paths like this when
304
# the repo is hosted at the root of the filesystem, i.e. in "/".
316
# the repo is hosted at the root of the filesystem, i.e. in "/".
305
317
if path.startswith('//'):
322
def _posix_path_from_environ(key):
323
"""Get unicode path from `key` in environment or None if not present
325
Note that posix systems use arbitrary byte strings for filesystem objects,
326
so a path that raises BadFilenameEncoding here may still be accessible.
328
val = os.environ.get(key, None)
329
if PY3 or val is None:
332
return val.decode(_fs_enc)
333
except UnicodeDecodeError:
334
# GZ 2011-12-12:Ideally want to include `key` in the exception message
335
raise errors.BadFilenameEncoding(val, _fs_enc)
310
338
def _posix_get_home_dir():
311
339
"""Get the home directory of the current user as a unicode path"""
312
340
path = posixpath.expanduser("~")
321
349
def _posix_getuser_unicode():
322
350
"""Get username from environment or password database as unicode"""
323
return getpass.getuser()
351
name = getpass.getuser()
354
user_encoding = get_user_encoding()
356
return name.decode(user_encoding)
357
except UnicodeDecodeError:
358
raise errors.BzrError("Encoding of username %r is unsupported by %s "
359
"application locale." % (name, user_encoding))
326
362
def _win32_fixdrive(path):
339
375
def _win32_abspath(path):
340
376
# Real ntpath.abspath doesn't have a problem with a unicode cwd
341
return _win32_fixdrive(ntpath.abspath(path).replace('\\', '/'))
377
return _win32_fixdrive(ntpath.abspath(unicode(path)).replace('\\', '/'))
344
380
def _win32_realpath(path):
345
381
# Real ntpath.realpath doesn't have a problem with a unicode cwd
346
return _win32_fixdrive(ntpath.realpath(path).replace('\\', '/'))
382
return _win32_fixdrive(ntpath.realpath(unicode(path)).replace('\\', '/'))
349
385
def _win32_pathjoin(*args):
464
503
exception = excinfo[1]
465
504
if function in (os.remove, os.rmdir) \
466
and isinstance(exception, OSError) \
467
and exception.errno == errno.EACCES:
505
and isinstance(exception, OSError) \
506
and exception.errno == errno.EACCES:
468
507
make_writable(path)
474
513
"""Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
514
return shutil.rmtree(path, ignore_errors, onerror)
516
f = win32utils.get_unicode_argv # special function or None
519
path_from_environ = win32utils.get_environ_unicode
477
520
_get_home_dir = win32utils.get_home_location
478
521
getuser_unicode = win32utils.get_user_name
504
547
output_encoding = get_user_encoding()
506
549
mutter('encoding stdout as osutils.get_user_encoding() %r',
509
552
output_encoding = input_encoding
511
554
mutter('encoding stdout as sys.stdin encoding %r',
515
558
mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
672
709
# writes fail on some platforms (e.g. Windows with SMB mounted
674
711
if not segment_size:
675
segment_size = 5242880 # 5MB
712
segment_size = 5242880 # 5MB
676
713
offsets = range(0, len(bytes), segment_size)
677
714
view = memoryview(bytes)
678
715
write = file_handle.write
679
716
for offset in offsets:
680
write(view[offset:offset + segment_size])
717
write(view[offset:offset+segment_size])
683
720
def file_iterator(input_file, readsize=32768):
747
778
for string in strings:
752
783
def sha_string(f, _factory=sha):
753
# GZ 2017-09-16: Dodgy if factory is ever not sha, probably shouldn't be.
754
return _hexdigest(_factory(f))
784
return _factory(f).hexdigest()
757
787
def fingerprint_file(f):
759
789
return {'size': len(b),
760
'sha1': _hexdigest(sha(b))}
790
'sha1': sha(b).hexdigest()}
763
793
def compare_files(a, b):
768
798
bi = b.read(BUFSIZE)
805
def gmtime(seconds=None):
806
"""Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.
807
GMT). When 'seconds' is not passed in, convert the current time instead.
808
Handy replacement for time.gmtime() buggy on Windows and 32-bit platforms.
811
seconds = time.time()
812
return (datetime(1970, 1, 1) + timedelta(seconds=seconds)).timetuple()
775
815
def local_time_offset(t=None):
776
816
"""Return offset of local zone from GMT, either at present or at time t."""
779
819
offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
780
820
return offset.days * 86400 + offset.seconds
783
822
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
784
823
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
797
836
:param show_offset: Whether to append the timezone.
799
838
(date_fmt, tt, offset_str) = \
800
_format_date(t, offset, timezone, date_fmt, show_offset)
839
_format_date(t, offset, timezone, date_fmt, show_offset)
801
840
date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
802
841
date_str = time.strftime(date_fmt, tt)
803
842
return date_str + offset_str
841
880
:param show_offset: Whether to append the timezone.
843
882
(date_fmt, tt, offset_str) = \
844
_format_date(t, offset, timezone, date_fmt, show_offset)
883
_format_date(t, offset, timezone, date_fmt, show_offset)
845
884
date_str = time.strftime(date_fmt, tt)
846
if not isinstance(date_str, str):
885
if not isinstance(date_str, text_type):
847
886
date_str = date_str.decode(get_user_encoding(), 'replace')
848
887
return date_str + offset_str
851
890
def _format_date(t, offset, timezone, date_fmt, show_offset):
852
891
if timezone == 'utc':
855
894
elif timezone == 'original':
856
895
if offset is None:
858
tt = time.gmtime(t + offset)
897
tt = gmtime(t + offset)
859
898
elif timezone == 'local':
860
899
tt = time.localtime(t)
861
900
offset = local_time_offset(t)
863
raise UnsupportedTimezoneFormat(timezone)
902
raise errors.UnsupportedTimezoneFormat(timezone)
864
903
if date_fmt is None:
865
904
date_fmt = "%a %Y-%m-%d %H:%M:%S"
902
941
plural_seconds = ''
904
943
plural_seconds = 's'
905
if minutes < 90: # print minutes, seconds up to 90 minutes
944
if minutes < 90: # print minutes, seconds up to 90 minutes
907
946
return '%d minute, %d second%s %s' % (
908
minutes, seconds, plural_seconds, direction)
947
minutes, seconds, plural_seconds, direction)
910
949
return '%d minutes, %d second%s %s' % (
911
minutes, seconds, plural_seconds, direction)
950
minutes, seconds, plural_seconds, direction)
913
952
hours = int(minutes / 60)
914
953
minutes -= 60 * hours
923
962
return '%d hours, %d minute%s %s' % (hours, minutes,
924
963
plural_minutes, direction)
928
966
"""Return size of given open file."""
929
967
return os.fstat(f.fileno())[stat.ST_SIZE]
932
# Alias os.urandom to support platforms (which?) without /dev/urandom and
970
# Alias os.urandom to support platforms (which?) without /dev/urandom and
933
971
# override if it doesn't work. Avoid checking on windows where there is
934
972
# significant initialisation cost that can be avoided for some bzr calls.
962
998
for raw_byte in rand_bytes(num):
963
s += ALNUM[raw_byte % 36]
1000
s += ALNUM[ord(raw_byte) % 36]
1002
s += ALNUM[raw_byte % 36]
967
# TODO: We could later have path objects that remember their list
968
# decomposition (might be too tricksy though.)
1006
## TODO: We could later have path objects that remember their list
1007
## decomposition (might be too tricksy though.)
970
1009
def splitpath(p):
971
1010
"""Turn string into list of parts."""
972
use_bytes = isinstance(p, bytes)
973
if os.path.sep == '\\':
974
# split on either delimiter because people might use either on
977
ps = re.split(b'[\\\\/]', p)
979
ps = re.split(r'[\\/]', p)
988
current_empty_dir = (b'.', b'')
991
current_empty_dir = ('.', '')
1011
# split on either delimiter because people might use either on
1013
ps = re.split(r'[\\/]', p)
996
1018
raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
997
elif f in current_empty_dir:
1019
elif (f == '.') or (f == ''):
1077
1099
"""Split s into lines, but without removing the newline characters."""
1078
1100
# Trivially convert a fulltext into a 'chunked' representation, and let
1079
1101
# chunks_to_lines do the heavy lifting.
1080
if isinstance(s, bytes):
1102
if isinstance(s, str):
1081
1103
# chunks_to_lines only supports 8-bit strings
1082
1104
return chunks_to_lines([s])
1120
1142
Will delete even if readonly.
1123
_delete_file_or_dir(path)
1145
_delete_file_or_dir(path)
1124
1146
except (OSError, IOError) as e:
1125
1147
if e.errno in (errno.EPERM, errno.EACCES):
1126
1148
# make writable and try again
1139
1161
# - root can damage a solaris file system by using unlink,
1140
1162
# - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1141
1163
# EACCES, OSX: EPERM) when invoked on a directory.
1142
if isdir(path): # Takes care of symlinks
1164
if isdir(path): # Takes care of symlinks
1145
1167
os.unlink(path)
1268
1290
abs_base = abspath(base)
1269
1291
current = abs_base
1292
_listdir = os.listdir
1271
1294
# use an explicit iterator so we can easily consume the rest on early exit.
1272
1295
bit_iter = iter(rel.split('/'))
1273
1296
for bit in bit_iter:
1274
1297
lbit = bit.lower()
1276
next_entries = scandir(current)
1277
except OSError: # enoent, eperm, etc
1299
next_entries = _listdir(current)
1300
except OSError: # enoent, eperm, etc
1278
1301
# We can't find this in the filesystem, so just append the
1279
1302
# remaining bits.
1280
1303
current = pathjoin(current, bit, *list(bit_iter))
1282
for entry in next_entries:
1283
if lbit == entry.name.lower():
1284
current = entry.path
1305
for look in next_entries:
1306
if lbit == look.lower():
1307
current = pathjoin(current, look)
1287
1310
# got to the end, nothing matched, so we just return the
1320
1341
Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1342
fails, a errors.BadFilenameEncoding exception is raised.
1323
if isinstance(filename, str):
1344
if isinstance(filename, text_type):
1324
1345
return filename
1326
1347
return filename.decode(_fs_enc)
1335
1356
Otherwise it is decoded from utf-8. If decoding fails, the exception is
1336
1357
wrapped in a BzrBadParameterNotUnicode exception.
1338
if isinstance(unicode_or_utf8_string, str):
1359
if isinstance(unicode_or_utf8_string, text_type):
1339
1360
return unicode_or_utf8_string
1341
1362
return unicode_or_utf8_string.decode('utf8')
1362
1383
return unicode_or_utf8_string.encode('utf-8')
1386
def safe_revision_id(unicode_or_utf8_string):
1387
"""Revision ids should now be utf8, but at one point they were unicode.
1389
:param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
1391
:return: None or a utf8 revision id.
1393
if (unicode_or_utf8_string is None
1394
or unicode_or_utf8_string.__class__ == bytes):
1395
return unicode_or_utf8_string
1396
raise TypeError('Unicode revision ids are no longer supported. '
1397
'Revision id generators should be creating utf8 revision '
1401
def safe_file_id(unicode_or_utf8_string):
1402
"""File ids should now be utf8, but at one point they were unicode.
1404
This is the same as safe_utf8, except it uses the cached encode functions
1405
to save a little bit of performance.
1407
:param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
1409
:return: None or a utf8 file id.
1411
if (unicode_or_utf8_string is None
1412
or unicode_or_utf8_string.__class__ == bytes):
1413
return unicode_or_utf8_string
1414
raise TypeError('Unicode file ids are no longer supported. '
1415
'File id generators should be creating utf8 file ids.')
1365
1418
_platform_normalizes_filenames = False
1366
1419
if sys.platform == 'darwin':
1367
1420
_platform_normalizes_filenames = True
1390
1443
can be accessed by that path.
1393
if isinstance(path, bytes):
1394
path = path.decode(sys.getfilesystemencoding())
1395
return unicodedata.normalize('NFC', path), True
1446
return unicodedata.normalize('NFC', text_type(path)), True
1398
1449
def _inaccessible_normalized_filename(path):
1399
1450
__doc__ = _accessible_normalized_filename.__doc__
1401
if isinstance(path, bytes):
1402
path = path.decode(sys.getfilesystemencoding())
1403
normalized = unicodedata.normalize('NFC', path)
1452
normalized = unicodedata.normalize('NFC', text_type(path))
1404
1453
return normalized, normalized == path
1549
1597
def _win32_terminal_size(width, height):
1550
width, height = win32utils.get_console_size(
1551
defaultx=width, defaulty=height)
1598
width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
1552
1599
return width, height
1555
1602
def _ioctl_terminal_size(width, height):
1604
import struct, fcntl, termios
1560
1605
s = struct.pack('HHHH', 0, 0, 0, 0)
1561
1606
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1562
1607
height, width = struct.unpack('HHHH', x)[0:2]
1580
1624
_terminal_size = _ioctl_terminal_size
1583
def supports_executable(path):
1584
"""Return if filesystem at path supports executable bit.
1586
:param path: Path for which to check the file system
1587
:return: boolean indicating whether executable bit can be stored/relied upon
1589
if sys.platform == 'win32':
1592
fs_type = get_fs_type(path)
1593
except errors.DependencyNotPresent as e:
1594
trace.mutter('Unable to get fs type for %r: %s', path, e)
1596
if fs_type in ('vfat', 'ntfs'):
1597
# filesystems known to not support executable bit
1602
def supports_symlinks(path):
1603
"""Return if the filesystem at path supports the creation of symbolic links.
1606
if not has_symlinks():
1609
fs_type = get_fs_type(path)
1610
except errors.DependencyNotPresent as e:
1611
trace.mutter('Unable to get fs type for %r: %s', path, e)
1613
if fs_type in ('vfat', 'ntfs'):
1614
# filesystems known to not support symlinks
1627
def supports_executable():
1628
return sys.platform != "win32"
1619
1631
def supports_posix_readonly():
1660
1674
raise errors.IllegalPath(path)
1663
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1667
scandir = os.scandir
1668
except AttributeError: # Python < 3
1669
lazy_import(globals(), """\
1670
from scandir import scandir
1677
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1674
1679
def _is_error_enotdir(e):
1675
1680
"""Check if this exception represents ENOTDIR.
1687
1692
:return: True if this represents an ENOTDIR error. False otherwise.
1689
1694
en = getattr(e, 'errno', None)
1690
if (en == errno.ENOTDIR or
1691
(sys.platform == 'win32' and
1692
(en == _WIN32_ERROR_DIRECTORY or
1694
and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1695
if (en == errno.ENOTDIR
1696
or (sys.platform == 'win32'
1697
and (en == _WIN32_ERROR_DIRECTORY
1698
or (en == errno.EINVAL
1699
and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1725
1730
rooted higher up.
1726
1731
:return: an iterator over the dirs.
1728
# TODO there is a bit of a smell where the results of the directory-
1733
#TODO there is a bit of a smell where the results of the directory-
1729
1734
# summary in this, and the path from the root, may not agree
1730
1735
# depending on top and prefix - i.e. ./foo and foo as a pair leads to
1731
1736
# potentially confusing output. We should make this more robust - but
1732
1737
# not at a speed cost. RBC 20060731
1733
1739
_directory = _directory_kind
1740
_listdir = os.listdir
1741
_kind_from_mode = file_kind_from_stat_mode
1734
1742
pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1736
1744
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1750
top_slash = top + u'/'
1753
append = dirblock.append
1746
for entry in scandir(top):
1747
name = decode_filename(entry.name)
1748
statvalue = entry.stat(follow_symlinks=False)
1749
kind = file_kind_from_stat_mode(statvalue.st_mode)
1750
dirblock.append((relprefix + name, name, kind, statvalue, entry.path))
1755
names = sorted(map(decode_filename, _listdir(top)))
1751
1756
except OSError as e:
1752
1757
if not _is_error_enotdir(e):
1754
except UnicodeDecodeError as e:
1755
raise errors.BadFilenameEncoding(e.object, _fs_enc)
1761
abspath = top_slash + name
1762
statvalue = _lstat(abspath)
1763
kind = _kind_from_mode(statvalue.st_mode)
1764
append((relprefix + name, name, kind, statvalue, abspath))
1757
1765
yield (relroot, top), dirblock
1759
1767
# push the user specified dirs from dirblock
1865
1873
See DirReader.read_dir for details.
1867
1875
_utf8_encode = self._utf8_encode
1869
def _fs_decode(s): return s.decode(_fs_enc)
1871
def _fs_encode(s): return s.encode(_fs_enc)
1877
_listdir = os.listdir
1878
_kind_from_mode = file_kind_from_stat_mode
1874
1881
relprefix = prefix + b'/'
1880
1887
append = dirblock.append
1881
for entry in scandir(safe_utf8(top)):
1888
for name in sorted(_listdir(top)):
1883
name = _fs_decode(entry.name)
1890
name_utf8 = _utf8_encode(name)[0]
1884
1891
except UnicodeDecodeError:
1885
1892
raise errors.BadFilenameEncoding(
1886
relprefix + entry.name, _fs_enc)
1893
_utf8_encode(relprefix)[0] + name, _fs_enc)
1887
1894
abspath = top_slash + name
1888
name_utf8 = _utf8_encode(name)[0]
1889
statvalue = entry.stat(follow_symlinks=False)
1890
kind = file_kind_from_stat_mode(statvalue.st_mode)
1895
statvalue = _lstat(abspath)
1896
kind = _kind_from_mode(statvalue.st_mode)
1891
1897
append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1892
return sorted(dirblock)
1895
1901
def copy_tree(from_path, to_path, handlers={}):
1920
1926
link_to = os.readlink(source)
1921
1927
os.symlink(link_to, dest)
1923
real_handlers = {'file': shutil.copy2,
1924
'symlink': copy_link,
1925
'directory': copy_dir,
1929
real_handlers = {'file':shutil.copy2,
1930
'symlink':copy_link,
1931
'directory':copy_dir,
1927
1933
real_handlers.update(handlers)
1929
1935
if not os.path.exists(to_path):
1965
1971
This can be used to sort paths in the same way that walkdirs does.
1967
return (dirname(path), path)
1973
return (dirname(path) , path)
1970
1976
def compare_paths_prefix_order(path_a, path_b):
1971
1977
"""Compare path_a and path_b to generate the same order walkdirs uses."""
1972
1978
key_a = path_prefix_key(path_a)
1973
1979
key_b = path_prefix_key(path_b)
1974
return (key_a > key_b) - (key_a < key_b)
1980
return cmp(key_a, key_b)
1977
1983
_cached_user_encoding = None
2058
2066
def read_bytes_from_socket(sock, report_activity=None,
2059
max_read_size=MAX_SOCKET_CHUNK):
2067
max_read_size=MAX_SOCKET_CHUNK):
2060
2068
"""Read up to max_read_size of bytes from sock and notify of progress.
2062
2070
Translates "Connection reset by peer" into file-like EOF (return an
2068
data = sock.recv(max_read_size)
2076
bytes = sock.recv(max_read_size)
2069
2077
except socket.error as e:
2070
2078
eno = e.args[0]
2071
2079
if eno in _end_of_stream_errors:
2072
2080
# The connection was closed by the other side. Callers expect
2073
2081
# an empty string to signal end-of-stream.
2075
2083
elif eno == errno.EINTR:
2076
2084
# Retry the interrupted recv.
2080
2088
if report_activity is not None:
2081
report_activity(len(data), 'read')
2089
report_activity(len(bytes), 'read')
2085
2093
def recv_all(socket, count):
2119
2127
view = memoryview(bytes)
2120
2128
while sent_total < byte_count:
2122
sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
2130
sent = sock.send(view[sent_total:sent_total+MAX_SOCKET_CHUNK])
2123
2131
except (socket.error, IOError) as e:
2124
2132
if e.args[0] in _end_of_stream_errors:
2125
2133
raise errors.ConnectionReset(
2203
2210
base = dirname(breezy.__file__)
2204
2211
if getattr(sys, 'frozen', None): # bzr.exe
2205
2212
base = abspath(pathjoin(base, '..', '..'))
2206
with open(pathjoin(base, resource_relpath), "rt") as f:
2213
f = file(pathjoin(base, resource_relpath), "rU")
2207
2215
return f.read()
2210
2219
def file_kind_from_stat_mode_thunk(mode):
2211
2220
global file_kind_from_stat_mode
2214
2223
from ._readdir_pyx import UTF8DirReader
2215
2224
file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2225
except ImportError as e:
2217
2226
# This is one time where we won't warn that an extension failed to
2218
2227
# load. The extension is never available on Windows anyway.
2219
2228
from ._readdir_py import (
2220
2229
_kind_from_mode as file_kind_from_stat_mode
2222
2231
return file_kind_from_stat_mode(mode)
2225
2232
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2228
2234
def file_stat(f, _lstat=os.lstat):
2234
2240
raise errors.NoSuchFile(f)
2238
2243
def file_kind(f, _lstat=os.lstat):
2239
2244
stat_value = file_stat(f, _lstat)
2240
2245
return file_kind_from_stat_mode(stat_value.st_mode)
2243
2247
def until_no_eintr(f, *a, **kw):
2244
2248
"""Run f(*a, **kw), retrying if an EINTR error occurs.
2296
2300
stdout=subprocess.PIPE).communicate()[0]
2297
2301
elif sys.platform == 'sunos5':
2298
2302
def _local_concurrency():
2299
return subprocess.Popen(['psrinfo', '-p', ],
2303
return subprocess.Popen(['psrinfo', '-p',],
2300
2304
stdout=subprocess.PIPE).communicate()[0]
2301
2305
elif sys.platform == "win32":
2302
2306
def _local_concurrency():
2437
2439
def find_executable_on_path(name):
2438
2440
"""Finds an executable on the PATH.
2440
2442
On Windows, this will try to append each extension in the PATHEXT
2441
2443
environment variable to the name, if it cannot be found with the name
2444
2446
:param name: The base name of the executable.
2445
2447
:return: The path to the executable found or None.
2484
2486
# exists, though not ours
2487
trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2489
mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2488
2490
# Don't really know.
2491
2493
# Exists and our process: not dead.
2495
2496
if sys.platform == "win32":
2496
2497
is_local_pid_dead = win32utils.is_local_pid_dead
2537
2538
raise exception_class(path)
2540
def read_mtab(path):
2541
"""Read an fstab-style file and extract mountpoint+filesystem information.
2543
:param path: Path to read from
2544
:yield: Tuples with mountpoints (as bytestrings) and filesystem names
2546
with open(path, 'rb') as f:
2548
if line.startswith(b'#'):
2553
yield cols[1], cols[2].decode('ascii', 'replace')
2556
MTAB_PATH = '/etc/mtab'
2558
class FilesystemFinder(object):
2559
"""Find the filesystem for a particular path."""
2561
def __init__(self, mountpoints):
2564
self._mountpoints = sorted(mountpoints, key=key, reverse=True)
2568
"""Create a FilesystemFinder from an mtab-style file.
2570
Note that this will silenty ignore mtab if it doesn't exist or can not
2573
# TODO(jelmer): Use inotify to be notified when /etc/mtab changes and
2574
# we need to re-read it.
2576
return cls(read_mtab(MTAB_PATH))
2577
except EnvironmentError as e:
2578
trace.mutter('Unable to read mtab: %s', e)
2581
def find(self, path):
2582
"""Find the filesystem used by a particular path.
2584
:param path: Path to find (bytestring or text type)
2585
:return: Filesystem name (as text type) or None, if the filesystem is
2588
for mountpoint, filesystem in self._mountpoints:
2589
if is_inside(mountpoint, path):
2594
_FILESYSTEM_FINDER = None
2597
def get_fs_type(path):
2598
"""Return the filesystem type for the partition a path is in.
2600
:param path: Path to search filesystem type for
2601
:return: A FS type, as string. E.g. "ext2"
2603
global _FILESYSTEM_FINDER
2604
if _FILESYSTEM_FINDER is None:
2605
_FILESYSTEM_FINDER = FilesystemFinder.from_mtab()
2607
if not isinstance(path, bytes):
2608
path = path.encode(_fs_enc)
2610
return _FILESYSTEM_FINDER.find(path)
2613
perf_counter = time.perf_counter
2541
def is_environment_error(evalue):
2542
"""True if exception instance is due to a process environment issue
2544
This includes OSError and IOError, but also other errors that come from
2545
the operating system or core libraries but are not subclasses of those.
2547
if isinstance(evalue, (EnvironmentError, select.error)):
2549
if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):