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
337
def _posix_path_from_environ(key):
338
"""Get unicode path from `key` in environment or None if not present
340
Note that posix systems use arbitrary byte strings for filesystem objects,
341
so a path that raises BadFilenameEncoding here may still be accessible.
343
val = os.environ.get(key, None)
344
if PY3 or val is None:
347
return val.decode(_fs_enc)
348
except UnicodeDecodeError:
349
# GZ 2011-12-12:Ideally want to include `key` in the exception message
350
raise errors.BadFilenameEncoding(val, _fs_enc)
310
353
def _posix_get_home_dir():
311
354
"""Get the home directory of the current user as a unicode path"""
312
355
path = posixpath.expanduser("~")
321
364
def _posix_getuser_unicode():
322
365
"""Get username from environment or password database as unicode"""
323
return getpass.getuser()
366
name = getpass.getuser()
369
user_encoding = get_user_encoding()
371
return name.decode(user_encoding)
372
except UnicodeDecodeError:
373
raise errors.BzrError("Encoding of username %r is unsupported by %s "
374
"application locale." % (name, user_encoding))
326
377
def _win32_fixdrive(path):
474
529
"""Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
530
return shutil.rmtree(path, ignore_errors, onerror)
532
f = win32utils.get_unicode_argv # special function or None
535
path_from_environ = win32utils.get_environ_unicode
477
536
_get_home_dir = win32utils.get_home_location
478
537
getuser_unicode = win32utils.get_user_name
843
906
(date_fmt, tt, offset_str) = \
844
907
_format_date(t, offset, timezone, date_fmt, show_offset)
845
908
date_str = time.strftime(date_fmt, tt)
846
if not isinstance(date_str, str):
909
if not isinstance(date_str, text_type):
847
910
date_str = date_str.decode(get_user_encoding(), 'replace')
848
911
return date_str + offset_str
970
1036
def splitpath(p):
971
1037
"""Turn string into list of parts."""
972
use_bytes = isinstance(p, bytes)
973
1038
if os.path.sep == '\\':
974
1039
# split on either delimiter because people might use either on
1041
if isinstance(p, bytes):
977
1042
ps = re.split(b'[\\\\/]', p)
979
1044
ps = re.split(r'[\\/]', p)
1046
if isinstance(p, bytes):
982
1047
ps = p.split(b'/')
984
1049
ps = p.split('/')
988
current_empty_dir = (b'.', b'')
991
current_empty_dir = ('.', '')
1053
if f in ('..', b'..'):
996
1054
raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
997
elif f in current_empty_dir:
1055
elif f in ('.', '', b'.', b''):
1268
1326
abs_base = abspath(base)
1269
1327
current = abs_base
1328
_listdir = os.listdir
1271
1330
# use an explicit iterator so we can easily consume the rest on early exit.
1272
1331
bit_iter = iter(rel.split('/'))
1273
1332
for bit in bit_iter:
1274
1333
lbit = bit.lower()
1276
next_entries = scandir(current)
1335
next_entries = _listdir(current)
1277
1336
except OSError: # enoent, eperm, etc
1278
1337
# We can't find this in the filesystem, so just append the
1279
1338
# remaining bits.
1280
1339
current = pathjoin(current, bit, *list(bit_iter))
1282
for entry in next_entries:
1283
if lbit == entry.name.lower():
1284
current = entry.path
1341
for look in next_entries:
1342
if lbit == look.lower():
1343
current = pathjoin(current, look)
1287
1346
# got to the end, nothing matched, so we just return the
1320
1379
Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1380
fails, a errors.BadFilenameEncoding exception is raised.
1323
if isinstance(filename, str):
1382
if isinstance(filename, text_type):
1324
1383
return filename
1326
1385
return filename.decode(_fs_enc)
1335
1394
Otherwise it is decoded from utf-8. If decoding fails, the exception is
1336
1395
wrapped in a BzrBadParameterNotUnicode exception.
1338
if isinstance(unicode_or_utf8_string, str):
1397
if isinstance(unicode_or_utf8_string, text_type):
1339
1398
return unicode_or_utf8_string
1341
1400
return unicode_or_utf8_string.decode('utf8')
1362
1421
return unicode_or_utf8_string.encode('utf-8')
1424
def safe_revision_id(unicode_or_utf8_string):
1425
"""Revision ids should now be utf8, but at one point they were unicode.
1427
:param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
1429
:return: None or a utf8 revision id.
1431
if (unicode_or_utf8_string is None
1432
or unicode_or_utf8_string.__class__ == bytes):
1433
return unicode_or_utf8_string
1434
raise TypeError('Unicode revision ids are no longer supported. '
1435
'Revision id generators should be creating utf8 revision '
1439
def safe_file_id(unicode_or_utf8_string):
1440
"""File ids should now be utf8, but at one point they were unicode.
1442
This is the same as safe_utf8, except it uses the cached encode functions
1443
to save a little bit of performance.
1445
:param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
1447
:return: None or a utf8 file id.
1449
if (unicode_or_utf8_string is None
1450
or unicode_or_utf8_string.__class__ == bytes):
1451
return unicode_or_utf8_string
1452
raise TypeError('Unicode file ids are no longer supported. '
1453
'File id generators should be creating utf8 file ids.')
1365
1456
_platform_normalizes_filenames = False
1366
1457
if sys.platform == 'darwin':
1367
1458
_platform_normalizes_filenames = True
1580
1671
_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
1674
def supports_executable():
1675
return sys.platform != "win32"
1619
1678
def supports_posix_readonly():
1663
1724
_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
1674
1727
def _is_error_enotdir(e):
1675
1728
"""Check if this exception represents ENOTDIR.
1730
1783
# depending on top and prefix - i.e. ./foo and foo as a pair leads to
1731
1784
# potentially confusing output. We should make this more robust - but
1732
1785
# not at a speed cost. RBC 20060731
1733
1787
_directory = _directory_kind
1788
_listdir = os.listdir
1789
_kind_from_mode = file_kind_from_stat_mode
1734
1790
pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1736
1792
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1798
top_slash = top + u'/'
1801
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))
1803
names = sorted(map(decode_filename, _listdir(top)))
1751
1804
except OSError as e:
1752
1805
if not _is_error_enotdir(e):
1754
except UnicodeDecodeError as e:
1755
raise errors.BadFilenameEncoding(e.object, _fs_enc)
1809
abspath = top_slash + name
1810
statvalue = _lstat(abspath)
1811
kind = _kind_from_mode(statvalue.st_mode)
1812
append((relprefix + name, name, kind, statvalue, abspath))
1757
1813
yield (relroot, top), dirblock
1759
1815
# push the user specified dirs from dirblock
1880
1939
append = dirblock.append
1881
for entry in scandir(safe_utf8(top)):
1940
for name_native in _listdir(top.encode('utf-8')):
1883
name = _fs_decode(entry.name)
1942
name = _fs_decode(name_native)
1884
1943
except UnicodeDecodeError:
1885
1944
raise errors.BadFilenameEncoding(
1886
relprefix + entry.name, _fs_enc)
1945
relprefix + name_native, _fs_enc)
1946
name_utf8 = _utf8_encode(name)[0]
1887
1947
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)
1948
statvalue = _lstat(abspath)
1949
kind = _kind_from_mode(statvalue.st_mode)
1891
1950
append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1892
1951
return sorted(dirblock)
2537
2598
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
2601
def is_environment_error(evalue):
2602
"""True if exception instance is due to a process environment issue
2604
This includes OSError and IOError, but also other errors that come from
2605
the operating system or core libraries but are not subclasses of those.
2607
if isinstance(evalue, (EnvironmentError, select.error)):
2609
if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):