/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/osutils.py

  • Committer: Jelmer Vernooij
  • Date: 2017-07-23 22:06:41 UTC
  • mfrom: (6738 trunk)
  • mto: This revision was merged to the branch mainline in revision 6739.
  • Revision ID: jelmer@jelmer.uk-20170723220641-69eczax9bmv8d6kk
Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
import errno
18
20
import os
19
21
import re
25
27
from .lazy_import import lazy_import
26
28
lazy_import(globals(), """
27
29
from datetime import datetime
 
30
from datetime import timedelta
28
31
import getpass
29
32
import locale
30
33
import ntpath
44
47
 
45
48
from breezy import (
46
49
    config,
 
50
    errors,
47
51
    trace,
48
52
    win32utils,
49
53
    )
50
54
from breezy.i18n import gettext
51
55
""")
52
56
 
 
57
from .sixish import (
 
58
    PY3,
 
59
    text_type,
 
60
    )
 
61
 
53
62
from hashlib import (
54
63
    md5,
55
64
    sha1 as sha,
57
66
 
58
67
 
59
68
import breezy
60
 
from . import (
61
 
    _fs_enc,
62
 
    errors,
63
 
    )
64
 
 
 
69
from . import _fs_enc
 
70
 
 
71
 
 
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
65
80
 
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)
75
90
 
76
91
 
77
 
class UnsupportedTimezoneFormat(errors.BzrError):
78
 
 
79
 
    _fmt = ('Unsupported timezone format "%(timezone)s", '
80
 
            'options are "utc", "original", "local".')
81
 
 
82
 
    def __init__(self, timezone):
83
 
        self.timezone = timezone
 
92
def get_unicode_argv():
 
93
    if PY3:
 
94
        return sys.argv[1:]
 
95
    try:
 
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))
84
101
 
85
102
 
86
103
def make_readonly(filename):
128
145
        return set(paths)
129
146
 
130
147
    def sort_key(path):
131
 
        if isinstance(path, bytes):
132
 
            return path.split(b'/')
133
 
        else:
134
 
            return path.split('/')
 
148
        return path.split('/')
135
149
    sorted_paths = sorted(list(paths), key=sort_key)
136
150
 
137
151
    search_paths = [sorted_paths[0]]
164
178
 
165
179
_directory_kind = 'directory'
166
180
 
167
 
 
168
181
def get_umask():
169
182
    """Return the current umask"""
170
183
    # Assume that people aren't messing with the umask while running
201
214
            return True
202
215
        except OSError as e:
203
216
            if e.errno == errno.ENOENT:
204
 
                return False
 
217
                return False;
205
218
            else:
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))
208
220
 
209
221
 
210
222
def fancy_rename(old, new, rename_func, unlink_func):
234
246
    file_existed = False
235
247
    try:
236
248
        rename_func(new, tmp_name)
237
 
    except (errors.NoSuchFile,):
 
249
    except (errors.NoSuchFile,) as e:
238
250
        pass
239
251
    except IOError as e:
240
252
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
244
256
            raise
245
257
    except Exception as e:
246
258
        if (getattr(e, 'errno', None) is None
247
 
                or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
259
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
248
260
            raise
249
261
    else:
250
262
        file_existed = True
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
266
278
            pass
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('//'):
306
318
        path = path[1:]
307
319
    return path
308
320
 
309
321
 
 
322
def _posix_path_from_environ(key):
 
323
    """Get unicode path from `key` in environment or None if not present
 
324
 
 
325
    Note that posix systems use arbitrary byte strings for filesystem objects,
 
326
    so a path that raises BadFilenameEncoding here may still be accessible.
 
327
    """
 
328
    val = os.environ.get(key, None)
 
329
    if PY3 or val is None:
 
330
        return val
 
331
    try:
 
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)
 
336
 
 
337
 
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("~")
320
348
 
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()
 
352
    if PY3:
 
353
        return name
 
354
    user_encoding = get_user_encoding()
 
355
    try:
 
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))
324
360
 
325
361
 
326
362
def _win32_fixdrive(path):
338
374
 
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('\\', '/'))
342
378
 
343
379
 
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('\\', '/'))
347
383
 
348
384
 
349
385
def _win32_pathjoin(*args):
351
387
 
352
388
 
353
389
def _win32_normpath(path):
354
 
    return _win32_fixdrive(ntpath.normpath(path).replace('\\', '/'))
 
390
    return _win32_fixdrive(ntpath.normpath(unicode(path)).replace('\\', '/'))
355
391
 
356
392
 
357
393
def _win32_getcwd():
396
432
            rename_func(old, new)
397
433
        except OSError as e:
398
434
            detailed_error = OSError(e.errno, e.strerror +
399
 
                                     " [occurred when renaming '%s' to '%s']" %
400
 
                                     (old, new))
 
435
                                " [occurred when renaming '%s' to '%s']" %
 
436
                                (old, new))
401
437
            detailed_error.old_filename = old
402
438
            detailed_error.new_filename = new
403
439
            raise detailed_error
405
441
    return _rename_wrapper
406
442
 
407
443
 
408
 
_getcwd = os.getcwd
 
444
if sys.version_info > (3,):
 
445
    _getcwd = os.getcwd
 
446
else:
 
447
    _getcwd = os.getcwdu
409
448
 
410
449
 
411
450
# Default rename wraps os.rename()
417
456
realpath = _posix_realpath
418
457
pathjoin = os.path.join
419
458
normpath = _posix_normpath
 
459
path_from_environ = _posix_path_from_environ
420
460
_get_home_dir = _posix_get_home_dir
421
461
getuser_unicode = _posix_getuser_unicode
422
462
getcwd = _getcwd
430
470
lstat = os.lstat
431
471
fstat = os.fstat
432
472
 
433
 
 
434
473
def wrap_stat(st):
435
474
    return st
436
475
 
463
502
        """
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)
469
508
            function(path)
470
509
        else:
474
513
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
514
        return shutil.rmtree(path, ignore_errors, onerror)
476
515
 
 
516
    f = win32utils.get_unicode_argv     # special function or None
 
517
    if f is not None:
 
518
        get_unicode_argv = f
 
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
479
522
 
504
547
            output_encoding = get_user_encoding()
505
548
            if trace:
506
549
                mutter('encoding stdout as osutils.get_user_encoding() %r',
507
 
                       output_encoding)
 
550
                   output_encoding)
508
551
        else:
509
552
            output_encoding = input_encoding
510
553
            if trace:
511
554
                mutter('encoding stdout as sys.stdin encoding %r',
512
 
                       output_encoding)
 
555
                    output_encoding)
513
556
    else:
514
557
        if trace:
515
558
            mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
518
561
        output_encoding = get_user_encoding()
519
562
        if trace:
520
563
            mutter('cp0 is invalid encoding.'
521
 
                   ' encoding stdout as osutils.get_user_encoding() %r',
522
 
                   output_encoding)
 
564
               ' encoding stdout as osutils.get_user_encoding() %r',
 
565
               output_encoding)
523
566
    # check encoding
524
567
    try:
525
568
        codecs.lookup(output_encoding)
528
571
                         ' unknown terminal encoding %s.\n'
529
572
                         '  Using encoding %s instead.\n'
530
573
                         % (output_encoding, get_user_encoding())
531
 
                         )
 
574
                        )
532
575
        output_encoding = get_user_encoding()
533
576
 
534
577
    return output_encoding
539
582
        F = realpath
540
583
    else:
541
584
        F = abspath
542
 
    [p, e] = os.path.split(f)
 
585
    [p,e] = os.path.split(f)
543
586
    if e == "" or e == "." or e == "..":
544
587
        return F(f)
545
588
    else:
561
604
    except OSError:
562
605
        return False
563
606
 
564
 
 
565
607
def islink(f):
566
608
    """True if f is a symlink."""
567
609
    try:
569
611
    except OSError:
570
612
        return False
571
613
 
572
 
 
573
614
def is_inside(dir, fname):
574
615
    """True if fname is inside dir.
575
616
 
585
626
    if dir == fname:
586
627
        return True
587
628
 
588
 
    if dir in ('', b''):
 
629
    if dir == '':
589
630
        return True
590
631
 
591
 
    if isinstance(dir, bytes):
592
 
        if not dir.endswith(b'/'):
593
 
            dir += b'/'
594
 
    else:
595
 
        if not dir.endswith('/'):
596
 
            dir += '/'
 
632
    if dir[-1] != '/':
 
633
        dir += '/'
597
634
 
598
635
    return fname.startswith(dir)
599
636
 
672
709
    # writes fail on some platforms (e.g. Windows with SMB  mounted
673
710
    # drives).
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])
681
718
 
682
719
 
683
720
def file_iterator(input_file, readsize=32768):
688
725
        yield b
689
726
 
690
727
 
691
 
# GZ 2017-09-16: Makes sense in general for hexdigest() result to be text, but
692
 
# used as bytes through most interfaces so encode with this wrapper.
693
 
def _hexdigest(hashobj):
694
 
    return hashobj.hexdigest().encode()
695
 
 
696
 
 
697
728
def sha_file(f):
698
729
    """Calculate the hexdigest of an open file.
699
730
 
700
731
    The file cursor should be already at the start.
701
732
    """
702
733
    s = sha()
703
 
    BUFSIZE = 128 << 10
 
734
    BUFSIZE = 128<<10
704
735
    while True:
705
736
        b = f.read(BUFSIZE)
706
737
        if not b:
707
738
            break
708
739
        s.update(b)
709
 
    return _hexdigest(s)
 
740
    return s.hexdigest()
710
741
 
711
742
 
712
743
def size_sha_file(f):
717
748
    """
718
749
    size = 0
719
750
    s = sha()
720
 
    BUFSIZE = 128 << 10
 
751
    BUFSIZE = 128<<10
721
752
    while True:
722
753
        b = f.read(BUFSIZE)
723
754
        if not b:
724
755
            break
725
756
        size += len(b)
726
757
        s.update(b)
727
 
    return size, _hexdigest(s)
 
758
    return size, s.hexdigest()
728
759
 
729
760
 
730
761
def sha_file_by_name(fname):
733
764
    f = os.open(fname, os.O_RDONLY | O_BINARY | O_NOINHERIT)
734
765
    try:
735
766
        while True:
736
 
            b = os.read(f, 1 << 16)
 
767
            b = os.read(f, 1<<16)
737
768
            if not b:
738
 
                return _hexdigest(s)
 
769
                return s.hexdigest()
739
770
            s.update(b)
740
771
    finally:
741
772
        os.close(f)
746
777
    s = _factory()
747
778
    for string in strings:
748
779
        s.update(string)
749
 
    return _hexdigest(s)
 
780
    return s.hexdigest()
750
781
 
751
782
 
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()
755
785
 
756
786
 
757
787
def fingerprint_file(f):
758
788
    b = f.read()
759
789
    return {'size': len(b),
760
 
            'sha1': _hexdigest(sha(b))}
 
790
            'sha1': sha(b).hexdigest()}
761
791
 
762
792
 
763
793
def compare_files(a, b):
768
798
        bi = b.read(BUFSIZE)
769
799
        if ai != bi:
770
800
            return False
771
 
        if not ai:
 
801
        if ai == '':
772
802
            return True
773
803
 
774
804
 
 
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.
 
809
    """
 
810
    if seconds is None:
 
811
        seconds = time.time()
 
812
    return (datetime(1970, 1, 1) + timedelta(seconds=seconds)).timetuple()
 
813
 
 
814
 
775
815
def local_time_offset(t=None):
776
816
    """Return offset of local zone from GMT, either at present or at time t."""
777
817
    if t is None:
779
819
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
780
820
    return offset.days * 86400 + offset.seconds
781
821
 
782
 
 
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]
785
824
 
797
836
    :param show_offset: Whether to append the timezone.
798
837
    """
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
808
847
 
809
848
 
810
849
def format_date_with_offset_in_original_timezone(t, offset=0,
811
 
                                                 _cache=_offset_cache):
 
850
    _cache=_offset_cache):
812
851
    """Return a formatted date string in the original timezone.
813
852
 
814
853
    This routine may be faster then format_date.
818
857
    """
819
858
    if offset is None:
820
859
        offset = 0
821
 
    tt = time.gmtime(t + offset)
 
860
    tt = gmtime(t + offset)
822
861
    date_fmt = _default_format_by_weekday_num[tt[6]]
823
862
    date_str = time.strftime(date_fmt, tt)
824
863
    offset_str = _cache.get(offset, None)
841
880
    :param show_offset: Whether to append the timezone.
842
881
    """
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
849
888
 
850
889
 
851
890
def _format_date(t, offset, timezone, date_fmt, show_offset):
852
891
    if timezone == 'utc':
853
 
        tt = time.gmtime(t)
 
892
        tt = gmtime(t)
854
893
        offset = 0
855
894
    elif timezone == 'original':
856
895
        if offset is None:
857
896
            offset = 0
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)
862
901
    else:
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"
866
905
    if show_offset:
871
910
 
872
911
 
873
912
def compact_date(when):
874
 
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
913
    return time.strftime('%Y%m%d%H%M%S', gmtime(when))
875
914
 
876
915
 
877
916
def format_delta(delta):
890
929
        delta = -delta
891
930
 
892
931
    seconds = delta
893
 
    if seconds < 90:  # print seconds up to 90 seconds
 
932
    if seconds < 90: # print seconds up to 90 seconds
894
933
        if seconds == 1:
895
934
            return '%d second %s' % (seconds, direction,)
896
935
        else:
902
941
        plural_seconds = ''
903
942
    else:
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
906
945
        if minutes == 1:
907
946
            return '%d minute, %d second%s %s' % (
908
 
                minutes, seconds, plural_seconds, direction)
 
947
                    minutes, seconds, plural_seconds, direction)
909
948
        else:
910
949
            return '%d minutes, %d second%s %s' % (
911
 
                minutes, seconds, plural_seconds, direction)
 
950
                    minutes, seconds, plural_seconds, direction)
912
951
 
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)
925
964
 
926
 
 
927
965
def filesize(f):
928
966
    """Return size of given open file."""
929
967
    return os.fstat(f.fileno())[stat.ST_SIZE]
930
968
 
931
969
 
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.
935
973
 
950
988
 
951
989
 
952
990
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
953
 
 
954
 
 
955
991
def rand_chars(num):
956
992
    """Return a random string of num alphanumeric characters
957
993
 
960
996
    """
961
997
    s = ''
962
998
    for raw_byte in rand_bytes(num):
963
 
        s += ALNUM[raw_byte % 36]
 
999
        if not PY3:
 
1000
            s += ALNUM[ord(raw_byte) % 36]
 
1001
        else:
 
1002
            s += ALNUM[raw_byte % 36]
964
1003
    return s
965
1004
 
966
1005
 
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.)
969
1008
 
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
975
 
        # Windows
976
 
        if use_bytes:
977
 
            ps = re.split(b'[\\\\/]', p)
978
 
        else:
979
 
            ps = re.split(r'[\\/]', p)
980
 
    else:
981
 
        if use_bytes:
982
 
            ps = p.split(b'/')
983
 
        else:
984
 
            ps = p.split('/')
985
 
 
986
 
    if use_bytes:
987
 
        parent_dir = b'..'
988
 
        current_empty_dir = (b'.', b'')
989
 
    else:
990
 
        parent_dir = '..'
991
 
        current_empty_dir = ('.', '')
 
1011
    # split on either delimiter because people might use either on
 
1012
    # Windows
 
1013
    ps = re.split(r'[\\/]', p)
992
1014
 
993
1015
    rps = []
994
1016
    for f in ps:
995
 
        if f == parent_dir:
 
1017
        if f == '..':
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 == ''):
998
1020
            pass
999
1021
        else:
1000
1022
            rps.append(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])
1083
1105
    else:
1120
1142
    Will delete even if readonly.
1121
1143
    """
1122
1144
    try:
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
1143
1165
        os.rmdir(path)
1144
1166
    else:
1145
1167
        os.unlink(path)
1224
1246
    if len(base) < MIN_ABS_PATHLENGTH:
1225
1247
        # must have space for e.g. a drive letter
1226
1248
        raise ValueError(gettext('%r is too short to calculate a relative path')
1227
 
                         % (base,))
 
1249
            % (base,))
1228
1250
 
1229
1251
    rp = abspath(path)
1230
1252
 
1267
1289
 
1268
1290
    abs_base = abspath(base)
1269
1291
    current = abs_base
 
1292
    _listdir = os.listdir
1270
1293
 
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()
1275
1298
        try:
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))
1281
1304
            break
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)
1285
1308
                break
1286
1309
        else:
1287
1310
            # got to the end, nothing matched, so we just return the
1291
1314
            break
1292
1315
    return current[len(abs_base):].lstrip('/')
1293
1316
 
1294
 
 
1295
1317
# XXX - TODO - we need better detection/integration of case-insensitive
1296
1318
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1297
1319
# filesystems), for example, so could probably benefit from the same basic
1302
1324
else:
1303
1325
    canonical_relpath = relpath
1304
1326
 
1305
 
 
1306
1327
def canonical_relpaths(base, paths):
1307
1328
    """Create an iterable to canonicalize a sequence of relative paths.
1308
1329
 
1320
1341
    Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1342
    fails, a errors.BadFilenameEncoding exception is raised.
1322
1343
    """
1323
 
    if isinstance(filename, str):
 
1344
    if isinstance(filename, text_type):
1324
1345
        return filename
1325
1346
    try:
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.
1337
1358
    """
1338
 
    if isinstance(unicode_or_utf8_string, str):
 
1359
    if isinstance(unicode_or_utf8_string, text_type):
1339
1360
        return unicode_or_utf8_string
1340
1361
    try:
1341
1362
        return unicode_or_utf8_string.decode('utf8')
1362
1383
    return unicode_or_utf8_string.encode('utf-8')
1363
1384
 
1364
1385
 
 
1386
def safe_revision_id(unicode_or_utf8_string):
 
1387
    """Revision ids should now be utf8, but at one point they were unicode.
 
1388
 
 
1389
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
 
1390
        utf8 or None).
 
1391
    :return: None or a utf8 revision id.
 
1392
    """
 
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 '
 
1398
                    'ids.')
 
1399
 
 
1400
 
 
1401
def safe_file_id(unicode_or_utf8_string):
 
1402
    """File ids should now be utf8, but at one point they were unicode.
 
1403
 
 
1404
    This is the same as safe_utf8, except it uses the cached encode functions
 
1405
    to save a little bit of performance.
 
1406
 
 
1407
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
 
1408
        utf8 or None).
 
1409
    :return: None or a utf8 file id.
 
1410
    """
 
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.')
 
1416
 
 
1417
 
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.
1391
1444
    """
1392
1445
 
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
1396
1447
 
1397
1448
 
1398
1449
def _inaccessible_normalized_filename(path):
1399
1450
    __doc__ = _accessible_normalized_filename.__doc__
1400
1451
 
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
1405
1454
 
1406
1455
 
1429
1478
    except AttributeError:
1430
1479
        # siginterrupt doesn't exist on this platform, or for this version
1431
1480
        # of Python.
1432
 
        def siginterrupt(signum, flag): return None
 
1481
        siginterrupt = lambda signum, flag: None
1433
1482
    if restart_syscall:
1434
1483
        def sig_handler(*args):
1435
1484
            # Python resets the siginterrupt flag when a signal is
1460
1509
_terminal_size_state = 'no_data'
1461
1510
_first_terminal_size = None
1462
1511
 
1463
 
 
1464
1512
def terminal_width():
1465
1513
    """Return terminal width.
1466
1514
 
1547
1595
 
1548
1596
 
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
1553
1600
 
1554
1601
 
1555
1602
def _ioctl_terminal_size(width, height):
1556
1603
    try:
1557
 
        import struct
1558
 
        import fcntl
1559
 
        import termios
 
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]
1564
1609
        pass
1565
1610
    return width, height
1566
1611
 
1567
 
 
1568
1612
_terminal_size = None
1569
1613
"""Returns the terminal size as (width, height).
1570
1614
 
1580
1624
    _terminal_size = _ioctl_terminal_size
1581
1625
 
1582
1626
 
1583
 
def supports_executable(path):
1584
 
    """Return if filesystem at path supports executable bit.
1585
 
 
1586
 
    :param path: Path for which to check the file system
1587
 
    :return: boolean indicating whether executable bit can be stored/relied upon
1588
 
    """
1589
 
    if sys.platform == 'win32':
1590
 
        return False
1591
 
    try:
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)
1595
 
    else:
1596
 
        if fs_type in ('vfat', 'ntfs'):
1597
 
            # filesystems known to not support executable bit
1598
 
            return False
1599
 
    return True
1600
 
 
1601
 
 
1602
 
def supports_symlinks(path):
1603
 
    """Return if the filesystem at path supports the creation of symbolic links.
1604
 
 
1605
 
    """
1606
 
    if not has_symlinks():
1607
 
        return False
1608
 
    try:
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)
1612
 
    else:
1613
 
        if fs_type in ('vfat', 'ntfs'):
1614
 
            # filesystems known to not support symlinks
1615
 
            return False
1616
 
    return True
 
1627
def supports_executable():
 
1628
    return sys.platform != "win32"
1617
1629
 
1618
1630
 
1619
1631
def supports_posix_readonly():
1642
1654
        if orig_val is not None:
1643
1655
            del os.environ[env_variable]
1644
1656
    else:
 
1657
        if not PY3 and isinstance(value, text_type):
 
1658
            value = value.encode(get_user_encoding())
1645
1659
        os.environ[env_variable] = value
1646
1660
    return orig_val
1647
1661
 
1660
1674
        raise errors.IllegalPath(path)
1661
1675
 
1662
1676
 
1663
 
_WIN32_ERROR_DIRECTORY = 267  # Similar to errno.ENOTDIR
1664
 
 
1665
 
 
1666
 
try:
1667
 
    scandir = os.scandir
1668
 
except AttributeError:  # Python < 3
1669
 
    lazy_import(globals(), """\
1670
 
from scandir import scandir
1671
 
""")
1672
 
 
 
1677
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1673
1678
 
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.
1688
1693
    """
1689
1694
    en = getattr(e, 'errno', None)
1690
 
    if (en == errno.ENOTDIR or
1691
 
        (sys.platform == 'win32' and
1692
 
            (en == _WIN32_ERROR_DIRECTORY or
1693
 
             (en == errno.EINVAL
1694
 
              and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1695
 
             ))):
 
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)
 
1700
        ))):
1696
1701
        return True
1697
1702
    return False
1698
1703
 
1725
1730
        rooted higher up.
1726
1731
    :return: an iterator over the dirs.
1727
1732
    """
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
 
1738
    _lstat = os.lstat
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))]
1735
1743
    while pending:
1736
1744
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1750
        top_slash = top + u'/'
1743
1751
 
1744
1752
        dirblock = []
 
1753
        append = dirblock.append
1745
1754
        try:
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):
1753
1758
                raise
1754
 
        except UnicodeDecodeError as e:
1755
 
            raise errors.BadFilenameEncoding(e.object, _fs_enc)
1756
 
        dirblock.sort()
 
1759
        else:
 
1760
            for name in names:
 
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
1758
1766
 
1759
1767
        # push the user specified dirs from dirblock
1865
1873
        See DirReader.read_dir for details.
1866
1874
        """
1867
1875
        _utf8_encode = self._utf8_encode
1868
 
 
1869
 
        def _fs_decode(s): return s.decode(_fs_enc)
1870
 
 
1871
 
        def _fs_encode(s): return s.encode(_fs_enc)
 
1876
        _lstat = os.lstat
 
1877
        _listdir = os.listdir
 
1878
        _kind_from_mode = file_kind_from_stat_mode
1872
1879
 
1873
1880
        if prefix:
1874
1881
            relprefix = prefix + b'/'
1878
1885
 
1879
1886
        dirblock = []
1880
1887
        append = dirblock.append
1881
 
        for entry in scandir(safe_utf8(top)):
 
1888
        for name in sorted(_listdir(top)):
1882
1889
            try:
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)
 
1898
        return dirblock
1893
1899
 
1894
1900
 
1895
1901
def copy_tree(from_path, to_path, handlers={}):
1920
1926
        link_to = os.readlink(source)
1921
1927
        os.symlink(link_to, dest)
1922
1928
 
1923
 
    real_handlers = {'file': shutil.copy2,
1924
 
                     'symlink': copy_link,
1925
 
                     'directory': copy_dir,
1926
 
                     }
 
1929
    real_handlers = {'file':shutil.copy2,
 
1930
                     'symlink':copy_link,
 
1931
                     'directory':copy_dir,
 
1932
                    }
1927
1933
    real_handlers.update(handlers)
1928
1934
 
1929
1935
    if not os.path.exists(to_path):
1944
1950
    if chown is None:
1945
1951
        return
1946
1952
 
1947
 
    if src is None:
 
1953
    if src == None:
1948
1954
        src = os.path.dirname(dst)
1949
1955
        if src == '':
1950
1956
            src = '.'
1952
1958
    try:
1953
1959
        s = os.stat(src)
1954
1960
        chown(dst, s.st_uid, s.st_gid)
1955
 
    except OSError:
 
1961
    except OSError as e:
1956
1962
        trace.warning(
1957
1963
            'Unable to copy ownership from "%s" to "%s". '
1958
1964
            'You may want to set it manually.', src, dst)
1964
1970
 
1965
1971
    This can be used to sort paths in the same way that walkdirs does.
1966
1972
    """
1967
 
    return (dirname(path), path)
 
1973
    return (dirname(path) , path)
1968
1974
 
1969
1975
 
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)
1975
1981
 
1976
1982
 
1977
1983
_cached_user_encoding = None
2008
2014
                             ' unknown encoding %s.'
2009
2015
                             ' Continuing with ascii encoding.\n'
2010
2016
                             % user_encoding
2011
 
                             )
 
2017
                            )
2012
2018
        user_encoding = 'ascii'
2013
2019
    else:
2014
2020
        # Get 'ascii' when setlocale has not been called or LANG=C or unset.
2038
2044
        return win32utils.get_host_name()
2039
2045
    else:
2040
2046
        import socket
2041
 
        return socket.gethostname()
 
2047
        if PY3:
 
2048
            return socket.gethostname()
 
2049
        return socket.gethostname().decode(get_user_encoding())
2042
2050
 
2043
2051
 
2044
2052
# We must not read/write any more than 64k at a time from/to a socket so we
2056
2064
 
2057
2065
 
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.
2061
2069
 
2062
2070
    Translates "Connection reset by peer" into file-like EOF (return an
2065
2073
    """
2066
2074
    while True:
2067
2075
        try:
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.
2074
 
                return b""
 
2082
                return ""
2075
2083
            elif eno == errno.EINTR:
2076
2084
                # Retry the interrupted recv.
2077
2085
                continue
2078
2086
            raise
2079
2087
        else:
2080
2088
            if report_activity is not None:
2081
 
                report_activity(len(data), 'read')
2082
 
            return data
 
2089
                report_activity(len(bytes), 'read')
 
2090
            return bytes
2083
2091
 
2084
2092
 
2085
2093
def recv_all(socket, count):
2092
2100
 
2093
2101
    This isn't optimized and is intended mostly for use in testing.
2094
2102
    """
2095
 
    b = b''
 
2103
    b = ''
2096
2104
    while len(b) < count:
2097
2105
        new = read_bytes_from_socket(socket, None, count - len(b))
2098
 
        if new == b'':
2099
 
            break  # eof
 
2106
        if new == '':
 
2107
            break # eof
2100
2108
        b += new
2101
2109
    return b
2102
2110
 
2119
2127
    view = memoryview(bytes)
2120
2128
    while sent_total < byte_count:
2121
2129
        try:
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(
2151
2159
            sock.connect(sa)
2152
2160
            return sock
2153
2161
 
2154
 
        except socket.error as e:
2155
 
            err = e
 
2162
        except socket.error as err:
2156
2163
            # 'err' is now the most recent error
2157
2164
            if sock is not None:
2158
2165
                sock.close()
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")
 
2214
    try:
2207
2215
        return f.read()
2208
 
 
 
2216
    finally:
 
2217
        f.close()
2209
2218
 
2210
2219
def file_kind_from_stat_mode_thunk(mode):
2211
2220
    global file_kind_from_stat_mode
2213
2222
        try:
2214
2223
            from ._readdir_pyx import UTF8DirReader
2215
2224
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2216
 
        except ImportError:
 
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
2221
2230
                )
2222
2231
    return file_kind_from_stat_mode(mode)
2223
 
 
2224
 
 
2225
2232
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2226
2233
 
2227
 
 
2228
2234
def file_stat(f, _lstat=os.lstat):
2229
2235
    try:
2230
2236
        # XXX cache?
2234
2240
            raise errors.NoSuchFile(f)
2235
2241
        raise
2236
2242
 
2237
 
 
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)
2241
2246
 
2242
 
 
2243
2247
def until_no_eintr(f, *a, **kw):
2244
2248
    """Run f(*a, **kw), retrying if an EINTR error occurs.
2245
2249
 
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():
2310
2314
 
2311
2315
_cached_local_concurrency = None
2312
2316
 
2313
 
 
2314
2317
def local_concurrency(use_cache=True):
2315
2318
    """Return how many processes can be run concurrently.
2316
2319
 
2338
2341
    except (TypeError, ValueError):
2339
2342
        concurrency = 1
2340
2343
    if use_cache:
2341
 
        _cached_local_concurrency = concurrency
 
2344
        _cached_concurrency = concurrency
2342
2345
    return concurrency
2343
2346
 
2344
2347
 
2356
2359
            data, _ = self.encode(object, self.errors)
2357
2360
            self.stream.write(data)
2358
2361
 
2359
 
 
2360
2362
if sys.platform == 'win32':
2361
2363
    def open_file(filename, mode='r', bufsize=-1):
2362
2364
        """This function is used to override the ``open`` builtin.
2390
2392
            else:
2391
2393
                flags |= os.O_WRONLY
2392
2394
            flags |= os.O_CREAT | os.O_APPEND
2393
 
        else:  # reading
 
2395
        else: #reading
2394
2396
            if updating:
2395
2397
                flags |= os.O_RDWR
2396
2398
            else:
2436
2438
 
2437
2439
def find_executable_on_path(name):
2438
2440
    """Finds an executable on the PATH.
2439
 
 
 
2441
    
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
2442
2444
    as given.
2443
 
 
 
2445
    
2444
2446
    :param name: The base name of the executable.
2445
2447
    :return: The path to the executable found or None.
2446
2448
    """
2484
2486
            # exists, though not ours
2485
2487
            return False
2486
2488
        else:
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.
2489
2491
            return False
2490
2492
    else:
2491
2493
        # Exists and our process: not dead.
2492
2494
        return False
2493
2495
 
2494
 
 
2495
2496
if sys.platform == "win32":
2496
2497
    is_local_pid_dead = win32utils.is_local_pid_dead
2497
2498
else:
2504
2505
 
2505
2506
def fdatasync(fileno):
2506
2507
    """Flush file contents to disk if possible.
2507
 
 
 
2508
    
2508
2509
    :param fileno: Integer OS file handle.
2509
2510
    :raises TransportNotPossible: If flushing to disk is not possible.
2510
2511
    """
2524
2525
 
2525
2526
def ensure_empty_directory_exists(path, exception_class):
2526
2527
    """Make sure a local directory exists and is empty.
2527
 
 
 
2528
    
2528
2529
    If it does not exist, it is created.  If it exists and is not empty, an
2529
2530
    instance of exception_class is raised.
2530
2531
    """
2537
2538
            raise exception_class(path)
2538
2539
 
2539
2540
 
2540
 
def read_mtab(path):
2541
 
    """Read an fstab-style file and extract mountpoint+filesystem information.
2542
 
 
2543
 
    :param path: Path to read from
2544
 
    :yield: Tuples with mountpoints (as bytestrings) and filesystem names
2545
 
    """
2546
 
    with open(path, 'rb') as f:
2547
 
        for line in f:
2548
 
            if line.startswith(b'#'):
2549
 
                continue
2550
 
            cols = line.split()
2551
 
            if len(cols) < 3:
2552
 
                continue
2553
 
            yield cols[1], cols[2].decode('ascii', 'replace')
2554
 
 
2555
 
 
2556
 
MTAB_PATH = '/etc/mtab'
2557
 
 
2558
 
class FilesystemFinder(object):
2559
 
    """Find the filesystem for a particular path."""
2560
 
 
2561
 
    def __init__(self, mountpoints):
2562
 
        def key(x):
2563
 
            return len(x[0])
2564
 
        self._mountpoints = sorted(mountpoints, key=key, reverse=True)
2565
 
 
2566
 
    @classmethod
2567
 
    def from_mtab(cls):
2568
 
        """Create a FilesystemFinder from an mtab-style file.
2569
 
 
2570
 
        Note that this will silenty ignore mtab if it doesn't exist or can not
2571
 
        be opened.
2572
 
        """
2573
 
        # TODO(jelmer): Use inotify to be notified when /etc/mtab changes and
2574
 
        # we need to re-read it.
2575
 
        try:
2576
 
            return cls(read_mtab(MTAB_PATH))
2577
 
        except EnvironmentError as e:
2578
 
            trace.mutter('Unable to read mtab: %s', e)
2579
 
            return cls([])
2580
 
 
2581
 
    def find(self, path):
2582
 
        """Find the filesystem used by a particular path.
2583
 
 
2584
 
        :param path: Path to find (bytestring or text type)
2585
 
        :return: Filesystem name (as text type) or None, if the filesystem is
2586
 
            unknown.
2587
 
        """
2588
 
        for mountpoint, filesystem in self._mountpoints:
2589
 
            if is_inside(mountpoint, path):
2590
 
                return filesystem
2591
 
        return None
2592
 
 
2593
 
 
2594
 
_FILESYSTEM_FINDER = None
2595
 
 
2596
 
 
2597
 
def get_fs_type(path):
2598
 
    """Return the filesystem type for the partition a path is in.
2599
 
 
2600
 
    :param path: Path to search filesystem type for
2601
 
    :return: A FS type, as string. E.g. "ext2"
2602
 
    """
2603
 
    global _FILESYSTEM_FINDER
2604
 
    if _FILESYSTEM_FINDER is None:
2605
 
        _FILESYSTEM_FINDER = FilesystemFinder.from_mtab()
2606
 
 
2607
 
    if not isinstance(path, bytes):
2608
 
        path = path.encode(_fs_enc)
2609
 
 
2610
 
    return _FILESYSTEM_FINDER.find(path)
2611
 
 
2612
 
 
2613
 
perf_counter = time.perf_counter
 
2541
def is_environment_error(evalue):
 
2542
    """True if exception instance is due to a process environment issue
 
2543
 
 
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.
 
2546
    """
 
2547
    if isinstance(evalue, (EnvironmentError, select.error)):
 
2548
        return True
 
2549
    if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
 
2550
        return True
 
2551
    return False