73
# Cross platform wall-clock time functionality with decent resolution.
74
# On Linux ``time.clock`` returns only CPU time. On Windows, ``time.time()``
75
# only has a resolution of ~15ms. Note that ``time.clock()`` is not
76
# synchronized with ``time.time()``, this is only meant to be used to find
77
# delta times by subtracting from another call to this function.
78
timer_func = time.time
79
if sys.platform == 'win32':
80
timer_func = time.clock
66
82
# On win32, O_BINARY is used to indicate the file should
67
83
# be opened in binary mode, rather than text mode.
68
84
# On other platforms, O_BINARY doesn't exist, because
83
99
self.timezone = timezone
102
def get_unicode_argv():
106
user_encoding = get_user_encoding()
107
return [a.decode(user_encoding) for a in sys.argv[1:]]
108
except UnicodeDecodeError:
109
raise errors.BzrError(gettext("Parameter {0!r} encoding is unsupported by {1} "
110
"application locale.").format(a, user_encoding))
86
113
def make_readonly(filename):
87
114
"""Make a filename read-only."""
88
115
mod = os.lstat(filename).st_mode
202
225
except OSError as e:
203
226
if e.errno == errno.ENOENT:
206
raise errors.BzrError(
207
gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
229
raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
210
232
def fancy_rename(old, new, rename_func, unlink_func):
260
282
# case-insensitive filesystem), so we may have accidentally renamed
261
283
# source by when we tried to rename target
262
284
if (file_existed and e.errno in (None, errno.ENOENT)
263
and old.lower() == new.lower()):
285
and old.lower() == new.lower()):
264
286
# source and target are the same file on a case-insensitive
265
287
# filesystem, so we don't generate an exception
301
323
# as a special case here by simply removing the first slash, as we consider
302
324
# that breaking POSIX compatibility for this obscure feature is acceptable.
303
325
# 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 "/".
326
# the repo is hosted at the root of the filesystem, i.e. in "/".
305
327
if path.startswith('//'):
332
def _posix_path_from_environ(key):
333
"""Get unicode path from `key` in environment or None if not present
335
Note that posix systems use arbitrary byte strings for filesystem objects,
336
so a path that raises BadFilenameEncoding here may still be accessible.
338
val = os.environ.get(key, None)
339
if PY3 or val is None:
342
return val.decode(_fs_enc)
343
except UnicodeDecodeError:
344
# GZ 2011-12-12:Ideally want to include `key` in the exception message
345
raise errors.BadFilenameEncoding(val, _fs_enc)
310
348
def _posix_get_home_dir():
311
349
"""Get the home directory of the current user as a unicode path"""
312
350
path = posixpath.expanduser("~")
321
359
def _posix_getuser_unicode():
322
360
"""Get username from environment or password database as unicode"""
323
return getpass.getuser()
361
name = getpass.getuser()
364
user_encoding = get_user_encoding()
366
return name.decode(user_encoding)
367
except UnicodeDecodeError:
368
raise errors.BzrError("Encoding of username %r is unsupported by %s "
369
"application locale." % (name, user_encoding))
326
372
def _win32_fixdrive(path):
339
385
def _win32_abspath(path):
340
386
# Real ntpath.abspath doesn't have a problem with a unicode cwd
341
return _win32_fixdrive(ntpath.abspath(path).replace('\\', '/'))
387
return _win32_fixdrive(ntpath.abspath(unicode(path)).replace('\\', '/'))
344
390
def _win32_realpath(path):
345
391
# Real ntpath.realpath doesn't have a problem with a unicode cwd
346
return _win32_fixdrive(ntpath.realpath(path).replace('\\', '/'))
392
return _win32_fixdrive(ntpath.realpath(unicode(path)).replace('\\', '/'))
349
395
def _win32_pathjoin(*args):
464
513
exception = excinfo[1]
465
514
if function in (os.remove, os.rmdir) \
466
and isinstance(exception, OSError) \
467
and exception.errno == errno.EACCES:
515
and isinstance(exception, OSError) \
516
and exception.errno == errno.EACCES:
468
517
make_writable(path)
474
523
"""Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
524
return shutil.rmtree(path, ignore_errors, onerror)
526
f = win32utils.get_unicode_argv # special function or None
529
path_from_environ = win32utils.get_environ_unicode
477
530
_get_home_dir = win32utils.get_home_location
478
531
getuser_unicode = win32utils.get_user_name
504
557
output_encoding = get_user_encoding()
506
559
mutter('encoding stdout as osutils.get_user_encoding() %r',
509
562
output_encoding = input_encoding
511
564
mutter('encoding stdout as sys.stdin encoding %r',
515
568
mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
672
719
# writes fail on some platforms (e.g. Windows with SMB mounted
674
721
if not segment_size:
675
segment_size = 5242880 # 5MB
722
segment_size = 5242880 # 5MB
676
723
offsets = range(0, len(bytes), segment_size)
677
724
view = memoryview(bytes)
678
725
write = file_handle.write
679
726
for offset in offsets:
680
write(view[offset:offset + segment_size])
727
write(view[offset:offset+segment_size])
683
730
def file_iterator(input_file, readsize=32768):
779
830
offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
780
831
return offset.days * 86400 + offset.seconds
783
833
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
784
834
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
797
847
:param show_offset: Whether to append the timezone.
799
849
(date_fmt, tt, offset_str) = \
800
_format_date(t, offset, timezone, date_fmt, show_offset)
850
_format_date(t, offset, timezone, date_fmt, show_offset)
801
851
date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
802
852
date_str = time.strftime(date_fmt, tt)
803
853
return date_str + offset_str
841
891
:param show_offset: Whether to append the timezone.
843
893
(date_fmt, tt, offset_str) = \
844
_format_date(t, offset, timezone, date_fmt, show_offset)
894
_format_date(t, offset, timezone, date_fmt, show_offset)
845
895
date_str = time.strftime(date_fmt, tt)
846
if not isinstance(date_str, str):
896
if not isinstance(date_str, text_type):
847
897
date_str = date_str.decode(get_user_encoding(), 'replace')
848
898
return date_str + offset_str
902
952
plural_seconds = ''
904
954
plural_seconds = 's'
905
if minutes < 90: # print minutes, seconds up to 90 minutes
955
if minutes < 90: # print minutes, seconds up to 90 minutes
907
957
return '%d minute, %d second%s %s' % (
908
minutes, seconds, plural_seconds, direction)
958
minutes, seconds, plural_seconds, direction)
910
960
return '%d minutes, %d second%s %s' % (
911
minutes, seconds, plural_seconds, direction)
961
minutes, seconds, plural_seconds, direction)
913
963
hours = int(minutes / 60)
914
964
minutes -= 60 * hours
923
973
return '%d hours, %d minute%s %s' % (hours, minutes,
924
974
plural_minutes, direction)
928
977
"""Return size of given open file."""
929
978
return os.fstat(f.fileno())[stat.ST_SIZE]
932
# Alias os.urandom to support platforms (which?) without /dev/urandom and
981
# Alias os.urandom to support platforms (which?) without /dev/urandom and
933
982
# override if it doesn't work. Avoid checking on windows where there is
934
983
# significant initialisation cost that can be avoided for some bzr calls.
962
1009
for raw_byte in rand_bytes(num):
963
s += ALNUM[raw_byte % 36]
1011
s += ALNUM[ord(raw_byte) % 36]
1013
s += ALNUM[raw_byte % 36]
967
# TODO: We could later have path objects that remember their list
968
# decomposition (might be too tricksy though.)
1017
## TODO: We could later have path objects that remember their list
1018
## decomposition (might be too tricksy though.)
970
1020
def splitpath(p):
971
1021
"""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 = ('.', '')
1022
# split on either delimiter because people might use either on
1024
ps = re.split(r'[\\/]', p)
996
1029
raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
997
elif f in current_empty_dir:
1030
elif (f == '.') or (f == ''):
1077
1110
"""Split s into lines, but without removing the newline characters."""
1078
1111
# Trivially convert a fulltext into a 'chunked' representation, and let
1079
1112
# chunks_to_lines do the heavy lifting.
1080
if isinstance(s, bytes):
1113
if isinstance(s, str):
1081
1114
# chunks_to_lines only supports 8-bit strings
1082
1115
return chunks_to_lines([s])
1120
1153
Will delete even if readonly.
1123
_delete_file_or_dir(path)
1156
_delete_file_or_dir(path)
1124
1157
except (OSError, IOError) as e:
1125
1158
if e.errno in (errno.EPERM, errno.EACCES):
1126
1159
# make writable and try again
1139
1172
# - root can damage a solaris file system by using unlink,
1140
1173
# - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1141
1174
# EACCES, OSX: EPERM) when invoked on a directory.
1142
if isdir(path): # Takes care of symlinks
1175
if isdir(path): # Takes care of symlinks
1145
1178
os.unlink(path)
1268
1301
abs_base = abspath(base)
1269
1302
current = abs_base
1303
_listdir = os.listdir
1271
1305
# use an explicit iterator so we can easily consume the rest on early exit.
1272
1306
bit_iter = iter(rel.split('/'))
1273
1307
for bit in bit_iter:
1274
1308
lbit = bit.lower()
1276
next_entries = scandir(current)
1277
except OSError: # enoent, eperm, etc
1310
next_entries = _listdir(current)
1311
except OSError: # enoent, eperm, etc
1278
1312
# We can't find this in the filesystem, so just append the
1279
1313
# remaining bits.
1280
1314
current = pathjoin(current, bit, *list(bit_iter))
1282
for entry in next_entries:
1283
if lbit == entry.name.lower():
1284
current = entry.path
1316
for look in next_entries:
1317
if lbit == look.lower():
1318
current = pathjoin(current, look)
1287
1321
# got to the end, nothing matched, so we just return the
1320
1352
Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1353
fails, a errors.BadFilenameEncoding exception is raised.
1323
if isinstance(filename, str):
1355
if isinstance(filename, text_type):
1324
1356
return filename
1326
1358
return filename.decode(_fs_enc)
1335
1367
Otherwise it is decoded from utf-8. If decoding fails, the exception is
1336
1368
wrapped in a BzrBadParameterNotUnicode exception.
1338
if isinstance(unicode_or_utf8_string, str):
1370
if isinstance(unicode_or_utf8_string, text_type):
1339
1371
return unicode_or_utf8_string
1341
1373
return unicode_or_utf8_string.decode('utf8')
1362
1394
return unicode_or_utf8_string.encode('utf-8')
1397
def safe_revision_id(unicode_or_utf8_string):
1398
"""Revision ids should now be utf8, but at one point they were unicode.
1400
:param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
1402
:return: None or a utf8 revision id.
1404
if (unicode_or_utf8_string is None
1405
or unicode_or_utf8_string.__class__ == bytes):
1406
return unicode_or_utf8_string
1407
raise TypeError('Unicode revision ids are no longer supported. '
1408
'Revision id generators should be creating utf8 revision '
1412
def safe_file_id(unicode_or_utf8_string):
1413
"""File ids should now be utf8, but at one point they were unicode.
1415
This is the same as safe_utf8, except it uses the cached encode functions
1416
to save a little bit of performance.
1418
:param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
1420
:return: None or a utf8 file id.
1422
if (unicode_or_utf8_string is None
1423
or unicode_or_utf8_string.__class__ == bytes):
1424
return unicode_or_utf8_string
1425
raise TypeError('Unicode file ids are no longer supported. '
1426
'File id generators should be creating utf8 file ids.')
1365
1429
_platform_normalizes_filenames = False
1366
1430
if sys.platform == 'darwin':
1367
1431
_platform_normalizes_filenames = True
1390
1454
can be accessed by that path.
1393
if isinstance(path, bytes):
1394
path = path.decode(sys.getfilesystemencoding())
1395
return unicodedata.normalize('NFC', path), True
1457
return unicodedata.normalize('NFC', text_type(path)), True
1398
1460
def _inaccessible_normalized_filename(path):
1399
1461
__doc__ = _accessible_normalized_filename.__doc__
1401
if isinstance(path, bytes):
1402
path = path.decode(sys.getfilesystemencoding())
1403
normalized = unicodedata.normalize('NFC', path)
1463
normalized = unicodedata.normalize('NFC', text_type(path))
1404
1464
return normalized, normalized == path
1549
1608
def _win32_terminal_size(width, height):
1550
width, height = win32utils.get_console_size(
1551
defaultx=width, defaulty=height)
1609
width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
1552
1610
return width, height
1555
1613
def _ioctl_terminal_size(width, height):
1615
import struct, fcntl, termios
1560
1616
s = struct.pack('HHHH', 0, 0, 0, 0)
1561
1617
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1562
1618
height, width = struct.unpack('HHHH', x)[0:2]
1580
1635
_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
1638
def supports_executable():
1639
return sys.platform != "win32"
1619
1642
def supports_posix_readonly():
1660
1685
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
1688
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1674
1690
def _is_error_enotdir(e):
1675
1691
"""Check if this exception represents ENOTDIR.
1687
1703
:return: True if this represents an ENOTDIR error. False otherwise.
1689
1705
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)
1706
if (en == errno.ENOTDIR
1707
or (sys.platform == 'win32'
1708
and (en == _WIN32_ERROR_DIRECTORY
1709
or (en == errno.EINVAL
1710
and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1725
1741
rooted higher up.
1726
1742
:return: an iterator over the dirs.
1728
# TODO there is a bit of a smell where the results of the directory-
1744
#TODO there is a bit of a smell where the results of the directory-
1729
1745
# summary in this, and the path from the root, may not agree
1730
1746
# depending on top and prefix - i.e. ./foo and foo as a pair leads to
1731
1747
# potentially confusing output. We should make this more robust - but
1732
1748
# not at a speed cost. RBC 20060731
1733
1750
_directory = _directory_kind
1751
_listdir = os.listdir
1752
_kind_from_mode = file_kind_from_stat_mode
1734
1753
pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1736
1755
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1761
top_slash = top + u'/'
1764
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))
1766
names = sorted(map(decode_filename, _listdir(top)))
1751
1767
except OSError as e:
1752
1768
if not _is_error_enotdir(e):
1754
except UnicodeDecodeError as e:
1755
raise errors.BadFilenameEncoding(e.object, _fs_enc)
1772
abspath = top_slash + name
1773
statvalue = _lstat(abspath)
1774
kind = _kind_from_mode(statvalue.st_mode)
1775
append((relprefix + name, name, kind, statvalue, abspath))
1757
1776
yield (relroot, top), dirblock
1759
1778
# push the user specified dirs from dirblock
1865
1884
See DirReader.read_dir for details.
1867
1886
_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)
1888
_listdir = os.listdir
1889
_kind_from_mode = file_kind_from_stat_mode
1874
1892
relprefix = prefix + b'/'
1880
1898
append = dirblock.append
1881
for entry in scandir(safe_utf8(top)):
1899
for name in sorted(_listdir(top)):
1883
name = _fs_decode(entry.name)
1901
name_utf8 = _utf8_encode(name)[0]
1884
1902
except UnicodeDecodeError:
1885
1903
raise errors.BadFilenameEncoding(
1886
relprefix + entry.name, _fs_enc)
1904
_utf8_encode(relprefix)[0] + name, _fs_enc)
1887
1905
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)
1906
statvalue = _lstat(abspath)
1907
kind = _kind_from_mode(statvalue.st_mode)
1891
1908
append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1892
return sorted(dirblock)
1895
1912
def copy_tree(from_path, to_path, handlers={}):
2058
2077
def read_bytes_from_socket(sock, report_activity=None,
2059
max_read_size=MAX_SOCKET_CHUNK):
2078
max_read_size=MAX_SOCKET_CHUNK):
2060
2079
"""Read up to max_read_size of bytes from sock and notify of progress.
2062
2081
Translates "Connection reset by peer" into file-like EOF (return an
2068
data = sock.recv(max_read_size)
2087
bytes = sock.recv(max_read_size)
2069
2088
except socket.error as e:
2070
2089
eno = e.args[0]
2071
2090
if eno in _end_of_stream_errors:
2072
2091
# The connection was closed by the other side. Callers expect
2073
2092
# an empty string to signal end-of-stream.
2075
2094
elif eno == errno.EINTR:
2076
2095
# Retry the interrupted recv.
2080
2099
if report_activity is not None:
2081
report_activity(len(data), 'read')
2100
report_activity(len(bytes), 'read')
2085
2104
def recv_all(socket, count):
2119
2138
view = memoryview(bytes)
2120
2139
while sent_total < byte_count:
2122
sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
2141
sent = sock.send(view[sent_total:sent_total+MAX_SOCKET_CHUNK])
2123
2142
except (socket.error, IOError) as e:
2124
2143
if e.args[0] in _end_of_stream_errors:
2125
2144
raise errors.ConnectionReset(
2203
2221
base = dirname(breezy.__file__)
2204
2222
if getattr(sys, 'frozen', None): # bzr.exe
2205
2223
base = abspath(pathjoin(base, '..', '..'))
2206
with open(pathjoin(base, resource_relpath), "rt") as f:
2224
f = file(pathjoin(base, resource_relpath), "rU")
2207
2226
return f.read()
2210
2230
def file_kind_from_stat_mode_thunk(mode):
2211
2231
global file_kind_from_stat_mode
2214
2234
from ._readdir_pyx import UTF8DirReader
2215
2235
file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2236
except ImportError as e:
2217
2237
# This is one time where we won't warn that an extension failed to
2218
2238
# load. The extension is never available on Windows anyway.
2219
2239
from ._readdir_py import (
2220
2240
_kind_from_mode as file_kind_from_stat_mode
2222
2242
return file_kind_from_stat_mode(mode)
2225
2243
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2228
2245
def file_stat(f, _lstat=os.lstat):
2234
2251
raise errors.NoSuchFile(f)
2238
2254
def file_kind(f, _lstat=os.lstat):
2239
2255
stat_value = file_stat(f, _lstat)
2240
2256
return file_kind_from_stat_mode(stat_value.st_mode)
2243
2258
def until_no_eintr(f, *a, **kw):
2244
2259
"""Run f(*a, **kw), retrying if an EINTR error occurs.
2296
2311
stdout=subprocess.PIPE).communicate()[0]
2297
2312
elif sys.platform == 'sunos5':
2298
2313
def _local_concurrency():
2299
return subprocess.Popen(['psrinfo', '-p', ],
2314
return subprocess.Popen(['psrinfo', '-p',],
2300
2315
stdout=subprocess.PIPE).communicate()[0]
2301
2316
elif sys.platform == "win32":
2302
2317
def _local_concurrency():
2437
2450
def find_executable_on_path(name):
2438
2451
"""Finds an executable on the PATH.
2440
2453
On Windows, this will try to append each extension in the PATHEXT
2441
2454
environment variable to the name, if it cannot be found with the name
2444
2457
:param name: The base name of the executable.
2445
2458
:return: The path to the executable found or None.
2484
2497
# exists, though not ours
2487
trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2500
mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2488
2501
# Don't really know.
2491
2504
# Exists and our process: not dead.
2495
2507
if sys.platform == "win32":
2496
2508
is_local_pid_dead = win32utils.is_local_pid_dead
2537
2549
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
2552
def is_environment_error(evalue):
2553
"""True if exception instance is due to a process environment issue
2555
This includes OSError and IOError, but also other errors that come from
2556
the operating system or core libraries but are not subclasses of those.
2558
if isinstance(evalue, (EnvironmentError, select.error)):
2560
if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):