/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: Martin
  • Date: 2018-11-16 19:09:31 UTC
  • mfrom: (7175 work)
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116190931-rmh7pk2an1zuecby
Merge trunk to resolve conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
107
107
        return [a.decode(user_encoding) for a in sys.argv[1:]]
108
108
    except UnicodeDecodeError:
109
109
        raise errors.BzrError(gettext("Parameter {0!r} encoding is unsupported by {1} "
110
 
            "application locale.").format(a, user_encoding))
 
110
                                      "application locale.").format(a, user_encoding))
111
111
 
112
112
 
113
113
def make_readonly(filename):
228
228
            return True
229
229
        except OSError as e:
230
230
            if e.errno == errno.ENOENT:
231
 
                return False;
 
231
                return False
232
232
            else:
233
 
                raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
 
233
                raise errors.BzrError(
 
234
                    gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
234
235
 
235
236
 
236
237
def fancy_rename(old, new, rename_func, unlink_func):
260
261
    file_existed = False
261
262
    try:
262
263
        rename_func(new, tmp_name)
263
 
    except (errors.NoSuchFile,) as e:
 
264
    except (errors.NoSuchFile,):
264
265
        pass
265
266
    except IOError as e:
266
267
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
270
271
            raise
271
272
    except Exception as e:
272
273
        if (getattr(e, 'errno', None) is None
273
 
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
274
                or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
274
275
            raise
275
276
    else:
276
277
        file_existed = True
286
287
        # case-insensitive filesystem), so we may have accidentally renamed
287
288
        # source by when we tried to rename target
288
289
        if (file_existed and e.errno in (None, errno.ENOENT)
289
 
            and old.lower() == new.lower()):
 
290
                and old.lower() == new.lower()):
290
291
            # source and target are the same file on a case-insensitive
291
292
            # filesystem, so we don't generate an exception
292
293
            pass
327
328
    # as a special case here by simply removing the first slash, as we consider
328
329
    # that breaking POSIX compatibility for this obscure feature is acceptable.
329
330
    # This is not a paranoid precaution, as we notably get paths like this when
330
 
    # the repo is hosted at the root of the filesystem, i.e. in "/".    
 
331
    # the repo is hosted at the root of the filesystem, i.e. in "/".
331
332
    if path.startswith('//'):
332
333
        path = path[1:]
333
334
    return path
370
371
        return name.decode(user_encoding)
371
372
    except UnicodeDecodeError:
372
373
        raise errors.BzrError("Encoding of username %r is unsupported by %s "
373
 
            "application locale." % (name, user_encoding))
 
374
                              "application locale." % (name, user_encoding))
374
375
 
375
376
 
376
377
def _win32_fixdrive(path):
446
447
            rename_func(old, new)
447
448
        except OSError as e:
448
449
            detailed_error = OSError(e.errno, e.strerror +
449
 
                                " [occurred when renaming '%s' to '%s']" %
450
 
                                (old, new))
 
450
                                     " [occurred when renaming '%s' to '%s']" %
 
451
                                     (old, new))
451
452
            detailed_error.old_filename = old
452
453
            detailed_error.new_filename = new
453
454
            raise detailed_error
517
518
        """
518
519
        exception = excinfo[1]
519
520
        if function in (os.remove, os.rmdir) \
520
 
            and isinstance(exception, OSError) \
521
 
            and exception.errno == errno.EACCES:
 
521
                and isinstance(exception, OSError) \
 
522
                and exception.errno == errno.EACCES:
522
523
            make_writable(path)
523
524
            function(path)
524
525
        else:
562
563
            output_encoding = get_user_encoding()
563
564
            if trace:
564
565
                mutter('encoding stdout as osutils.get_user_encoding() %r',
565
 
                   output_encoding)
 
566
                       output_encoding)
566
567
        else:
567
568
            output_encoding = input_encoding
568
569
            if trace:
569
570
                mutter('encoding stdout as sys.stdin encoding %r',
570
 
                    output_encoding)
 
571
                       output_encoding)
571
572
    else:
572
573
        if trace:
573
574
            mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
576
577
        output_encoding = get_user_encoding()
577
578
        if trace:
578
579
            mutter('cp0 is invalid encoding.'
579
 
               ' encoding stdout as osutils.get_user_encoding() %r',
580
 
               output_encoding)
 
580
                   ' encoding stdout as osutils.get_user_encoding() %r',
 
581
                   output_encoding)
581
582
    # check encoding
582
583
    try:
583
584
        codecs.lookup(output_encoding)
586
587
                         ' unknown terminal encoding %s.\n'
587
588
                         '  Using encoding %s instead.\n'
588
589
                         % (output_encoding, get_user_encoding())
589
 
                        )
 
590
                         )
590
591
        output_encoding = get_user_encoding()
591
592
 
592
593
    return output_encoding
730
731
    # writes fail on some platforms (e.g. Windows with SMB  mounted
731
732
    # drives).
732
733
    if not segment_size:
733
 
        segment_size = 5242880 # 5MB
 
734
        segment_size = 5242880  # 5MB
734
735
    offsets = range(0, len(bytes), segment_size)
735
736
    view = memoryview(bytes)
736
737
    write = file_handle.write
737
738
    for offset in offsets:
738
 
        write(view[offset:offset+segment_size])
 
739
        write(view[offset:offset + segment_size])
739
740
 
740
741
 
741
742
def file_iterator(input_file, readsize=32768):
762
763
    The file cursor should be already at the start.
763
764
    """
764
765
    s = sha()
765
 
    BUFSIZE = 128<<10
 
766
    BUFSIZE = 128 << 10
766
767
    while True:
767
768
        b = f.read(BUFSIZE)
768
769
        if not b:
779
780
    """
780
781
    size = 0
781
782
    s = sha()
782
 
    BUFSIZE = 128<<10
 
783
    BUFSIZE = 128 << 10
783
784
    while True:
784
785
        b = f.read(BUFSIZE)
785
786
        if not b:
795
796
    f = os.open(fname, os.O_RDONLY | O_BINARY | O_NOINHERIT)
796
797
    try:
797
798
        while True:
798
 
            b = os.read(f, 1<<16)
 
799
            b = os.read(f, 1 << 16)
799
800
            if not b:
800
801
                return _hexdigest(s)
801
802
            s.update(b)
841
842
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
842
843
    return offset.days * 86400 + offset.seconds
843
844
 
 
845
 
844
846
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
845
847
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
846
848
 
858
860
    :param show_offset: Whether to append the timezone.
859
861
    """
860
862
    (date_fmt, tt, offset_str) = \
861
 
               _format_date(t, offset, timezone, date_fmt, show_offset)
 
863
        _format_date(t, offset, timezone, date_fmt, show_offset)
862
864
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
863
865
    date_str = time.strftime(date_fmt, tt)
864
866
    return date_str + offset_str
869
871
 
870
872
 
871
873
def format_date_with_offset_in_original_timezone(t, offset=0,
872
 
    _cache=_offset_cache):
 
874
                                                 _cache=_offset_cache):
873
875
    """Return a formatted date string in the original timezone.
874
876
 
875
877
    This routine may be faster then format_date.
902
904
    :param show_offset: Whether to append the timezone.
903
905
    """
904
906
    (date_fmt, tt, offset_str) = \
905
 
               _format_date(t, offset, timezone, date_fmt, show_offset)
 
907
        _format_date(t, offset, timezone, date_fmt, show_offset)
906
908
    date_str = time.strftime(date_fmt, tt)
907
909
    if not isinstance(date_str, text_type):
908
910
        date_str = date_str.decode(get_user_encoding(), 'replace')
951
953
        delta = -delta
952
954
 
953
955
    seconds = delta
954
 
    if seconds < 90: # print seconds up to 90 seconds
 
956
    if seconds < 90:  # print seconds up to 90 seconds
955
957
        if seconds == 1:
956
958
            return '%d second %s' % (seconds, direction,)
957
959
        else:
963
965
        plural_seconds = ''
964
966
    else:
965
967
        plural_seconds = 's'
966
 
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
968
    if minutes < 90:  # print minutes, seconds up to 90 minutes
967
969
        if minutes == 1:
968
970
            return '%d minute, %d second%s %s' % (
969
 
                    minutes, seconds, plural_seconds, direction)
 
971
                minutes, seconds, plural_seconds, direction)
970
972
        else:
971
973
            return '%d minutes, %d second%s %s' % (
972
 
                    minutes, seconds, plural_seconds, direction)
 
974
                minutes, seconds, plural_seconds, direction)
973
975
 
974
976
    hours = int(minutes / 60)
975
977
    minutes -= 60 * hours
984
986
    return '%d hours, %d minute%s %s' % (hours, minutes,
985
987
                                         plural_minutes, direction)
986
988
 
 
989
 
987
990
def filesize(f):
988
991
    """Return size of given open file."""
989
992
    return os.fstat(f.fileno())[stat.ST_SIZE]
990
993
 
991
994
 
992
 
# Alias os.urandom to support platforms (which?) without /dev/urandom and 
 
995
# Alias os.urandom to support platforms (which?) without /dev/urandom and
993
996
# override if it doesn't work. Avoid checking on windows where there is
994
997
# significant initialisation cost that can be avoided for some bzr calls.
995
998
 
1010
1013
 
1011
1014
 
1012
1015
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
 
1016
 
 
1017
 
1013
1018
def rand_chars(num):
1014
1019
    """Return a random string of num alphanumeric characters
1015
1020
 
1025
1030
    return s
1026
1031
 
1027
1032
 
1028
 
## TODO: We could later have path objects that remember their list
1029
 
## decomposition (might be too tricksy though.)
 
1033
# TODO: We could later have path objects that remember their list
 
1034
# decomposition (might be too tricksy though.)
1030
1035
 
1031
1036
def splitpath(p):
1032
1037
    """Turn string into list of parts."""
1167
1172
    Will delete even if readonly.
1168
1173
    """
1169
1174
    try:
1170
 
       _delete_file_or_dir(path)
 
1175
        _delete_file_or_dir(path)
1171
1176
    except (OSError, IOError) as e:
1172
1177
        if e.errno in (errno.EPERM, errno.EACCES):
1173
1178
            # make writable and try again
1186
1191
    # - root can damage a solaris file system by using unlink,
1187
1192
    # - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1188
1193
    #   EACCES, OSX: EPERM) when invoked on a directory.
1189
 
    if isdir(path): # Takes care of symlinks
 
1194
    if isdir(path):  # Takes care of symlinks
1190
1195
        os.rmdir(path)
1191
1196
    else:
1192
1197
        os.unlink(path)
1271
1276
    if len(base) < MIN_ABS_PATHLENGTH:
1272
1277
        # must have space for e.g. a drive letter
1273
1278
        raise ValueError(gettext('%r is too short to calculate a relative path')
1274
 
            % (base,))
 
1279
                         % (base,))
1275
1280
 
1276
1281
    rp = abspath(path)
1277
1282
 
1322
1327
        lbit = bit.lower()
1323
1328
        try:
1324
1329
            next_entries = _listdir(current)
1325
 
        except OSError: # enoent, eperm, etc
 
1330
        except OSError:  # enoent, eperm, etc
1326
1331
            # We can't find this in the filesystem, so just append the
1327
1332
            # remaining bits.
1328
1333
            current = pathjoin(current, bit, *list(bit_iter))
1339
1344
            break
1340
1345
    return current[len(abs_base):].lstrip('/')
1341
1346
 
 
1347
 
1342
1348
# XXX - TODO - we need better detection/integration of case-insensitive
1343
1349
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1344
1350
# filesystems), for example, so could probably benefit from the same basic
1349
1355
else:
1350
1356
    canonical_relpath = relpath
1351
1357
 
 
1358
 
1352
1359
def canonical_relpaths(base, paths):
1353
1360
    """Create an iterable to canonicalize a sequence of relative paths.
1354
1361
 
1416
1423
    :return: None or a utf8 revision id.
1417
1424
    """
1418
1425
    if (unicode_or_utf8_string is None
1419
 
        or unicode_or_utf8_string.__class__ == bytes):
 
1426
            or unicode_or_utf8_string.__class__ == bytes):
1420
1427
        return unicode_or_utf8_string
1421
1428
    raise TypeError('Unicode revision ids are no longer supported. '
1422
1429
                    'Revision id generators should be creating utf8 revision '
1434
1441
    :return: None or a utf8 file id.
1435
1442
    """
1436
1443
    if (unicode_or_utf8_string is None
1437
 
        or unicode_or_utf8_string.__class__ == bytes):
 
1444
            or unicode_or_utf8_string.__class__ == bytes):
1438
1445
        return unicode_or_utf8_string
1439
1446
    raise TypeError('Unicode file ids are no longer supported. '
1440
1447
                    'File id generators should be creating utf8 file ids.')
1507
1514
    except AttributeError:
1508
1515
        # siginterrupt doesn't exist on this platform, or for this version
1509
1516
        # of Python.
1510
 
        siginterrupt = lambda signum, flag: None
 
1517
        def siginterrupt(signum, flag): return None
1511
1518
    if restart_syscall:
1512
1519
        def sig_handler(*args):
1513
1520
            # Python resets the siginterrupt flag when a signal is
1538
1545
_terminal_size_state = 'no_data'
1539
1546
_first_terminal_size = None
1540
1547
 
 
1548
 
1541
1549
def terminal_width():
1542
1550
    """Return terminal width.
1543
1551
 
1624
1632
 
1625
1633
 
1626
1634
def _win32_terminal_size(width, height):
1627
 
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
 
1635
    width, height = win32utils.get_console_size(
 
1636
        defaultx=width, defaulty=height)
1628
1637
    return width, height
1629
1638
 
1630
1639
 
1631
1640
def _ioctl_terminal_size(width, height):
1632
1641
    try:
1633
 
        import struct, fcntl, termios
 
1642
        import struct
 
1643
        import fcntl
 
1644
        import termios
1634
1645
        s = struct.pack('HHHH', 0, 0, 0, 0)
1635
1646
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1636
1647
        height, width = struct.unpack('HHHH', x)[0:2]
1638
1649
        pass
1639
1650
    return width, height
1640
1651
 
 
1652
 
1641
1653
_terminal_size = None
1642
1654
"""Returns the terminal size as (width, height).
1643
1655
 
1703
1715
        raise errors.IllegalPath(path)
1704
1716
 
1705
1717
 
1706
 
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
 
1718
_WIN32_ERROR_DIRECTORY = 267  # Similar to errno.ENOTDIR
 
1719
 
1707
1720
 
1708
1721
def _is_error_enotdir(e):
1709
1722
    """Check if this exception represents ENOTDIR.
1721
1734
    :return: True if this represents an ENOTDIR error. False otherwise.
1722
1735
    """
1723
1736
    en = getattr(e, 'errno', None)
1724
 
    if (en == errno.ENOTDIR
1725
 
        or (sys.platform == 'win32'
1726
 
            and (en == _WIN32_ERROR_DIRECTORY
1727
 
                 or (en == errno.EINVAL
1728
 
                     and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1729
 
        ))):
 
1737
    if (en == errno.ENOTDIR or
 
1738
        (sys.platform == 'win32' and
 
1739
            (en == _WIN32_ERROR_DIRECTORY or
 
1740
             (en == errno.EINVAL
 
1741
              and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
 
1742
             ))):
1730
1743
        return True
1731
1744
    return False
1732
1745
 
1759
1772
        rooted higher up.
1760
1773
    :return: an iterator over the dirs.
1761
1774
    """
1762
 
    #TODO there is a bit of a smell where the results of the directory-
 
1775
    # TODO there is a bit of a smell where the results of the directory-
1763
1776
    # summary in this, and the path from the root, may not agree
1764
1777
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1765
1778
    # potentially confusing output. We should make this more robust - but
1902
1915
        See DirReader.read_dir for details.
1903
1916
        """
1904
1917
        _utf8_encode = self._utf8_encode
1905
 
        _fs_decode = lambda s: s.decode(_fs_enc)
1906
 
        _fs_encode = lambda s: s.encode(_fs_enc)
 
1918
 
 
1919
        def _fs_decode(s): return s.decode(_fs_enc)
 
1920
 
 
1921
        def _fs_encode(s): return s.encode(_fs_enc)
1907
1922
        _lstat = os.lstat
1908
1923
        _listdir = os.listdir
1909
1924
        _kind_from_mode = file_kind_from_stat_mode
1961
1976
    real_handlers = {'file': shutil.copy2,
1962
1977
                     'symlink': copy_link,
1963
1978
                     'directory': copy_dir,
1964
 
                    }
 
1979
                     }
1965
1980
    real_handlers.update(handlers)
1966
1981
 
1967
1982
    if not os.path.exists(to_path):
1982
1997
    if chown is None:
1983
1998
        return
1984
1999
 
1985
 
    if src == None:
 
2000
    if src is None:
1986
2001
        src = os.path.dirname(dst)
1987
2002
        if src == '':
1988
2003
            src = '.'
1990
2005
    try:
1991
2006
        s = os.stat(src)
1992
2007
        chown(dst, s.st_uid, s.st_gid)
1993
 
    except OSError as e:
 
2008
    except OSError:
1994
2009
        trace.warning(
1995
2010
            'Unable to copy ownership from "%s" to "%s". '
1996
2011
            'You may want to set it manually.', src, dst)
2046
2061
                             ' unknown encoding %s.'
2047
2062
                             ' Continuing with ascii encoding.\n'
2048
2063
                             % user_encoding
2049
 
                            )
 
2064
                             )
2050
2065
        user_encoding = 'ascii'
2051
2066
    else:
2052
2067
        # Get 'ascii' when setlocale has not been called or LANG=C or unset.
2096
2111
 
2097
2112
 
2098
2113
def read_bytes_from_socket(sock, report_activity=None,
2099
 
        max_read_size=MAX_SOCKET_CHUNK):
 
2114
                           max_read_size=MAX_SOCKET_CHUNK):
2100
2115
    """Read up to max_read_size of bytes from sock and notify of progress.
2101
2116
 
2102
2117
    Translates "Connection reset by peer" into file-like EOF (return an
2136
2151
    while len(b) < count:
2137
2152
        new = read_bytes_from_socket(socket, None, count - len(b))
2138
2153
        if new == b'':
2139
 
            break # eof
 
2154
            break  # eof
2140
2155
        b += new
2141
2156
    return b
2142
2157
 
2159
2174
    view = memoryview(bytes)
2160
2175
    while sent_total < byte_count:
2161
2176
        try:
2162
 
            sent = sock.send(view[sent_total:sent_total+MAX_SOCKET_CHUNK])
 
2177
            sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
2163
2178
        except (socket.error, IOError) as e:
2164
2179
            if e.args[0] in _end_of_stream_errors:
2165
2180
                raise errors.ConnectionReset(
2246
2261
    with open(pathjoin(base, resource_relpath), "rt") as f:
2247
2262
        return f.read()
2248
2263
 
 
2264
 
2249
2265
def file_kind_from_stat_mode_thunk(mode):
2250
2266
    global file_kind_from_stat_mode
2251
2267
    if file_kind_from_stat_mode is file_kind_from_stat_mode_thunk:
2252
2268
        try:
2253
2269
            from ._readdir_pyx import UTF8DirReader
2254
2270
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2255
 
        except ImportError as e:
 
2271
        except ImportError:
2256
2272
            # This is one time where we won't warn that an extension failed to
2257
2273
            # load. The extension is never available on Windows anyway.
2258
2274
            from ._readdir_py import (
2259
2275
                _kind_from_mode as file_kind_from_stat_mode
2260
2276
                )
2261
2277
    return file_kind_from_stat_mode(mode)
 
2278
 
 
2279
 
2262
2280
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2263
2281
 
 
2282
 
2264
2283
def file_stat(f, _lstat=os.lstat):
2265
2284
    try:
2266
2285
        # XXX cache?
2270
2289
            raise errors.NoSuchFile(f)
2271
2290
        raise
2272
2291
 
 
2292
 
2273
2293
def file_kind(f, _lstat=os.lstat):
2274
2294
    stat_value = file_stat(f, _lstat)
2275
2295
    return file_kind_from_stat_mode(stat_value.st_mode)
2276
2296
 
 
2297
 
2277
2298
def until_no_eintr(f, *a, **kw):
2278
2299
    """Run f(*a, **kw), retrying if an EINTR error occurs.
2279
2300
 
2330
2351
                                stdout=subprocess.PIPE).communicate()[0]
2331
2352
elif sys.platform == 'sunos5':
2332
2353
    def _local_concurrency():
2333
 
        return subprocess.Popen(['psrinfo', '-p',],
 
2354
        return subprocess.Popen(['psrinfo', '-p', ],
2334
2355
                                stdout=subprocess.PIPE).communicate()[0]
2335
2356
elif sys.platform == "win32":
2336
2357
    def _local_concurrency():
2344
2365
 
2345
2366
_cached_local_concurrency = None
2346
2367
 
 
2368
 
2347
2369
def local_concurrency(use_cache=True):
2348
2370
    """Return how many processes can be run concurrently.
2349
2371
 
2371
2393
    except (TypeError, ValueError):
2372
2394
        concurrency = 1
2373
2395
    if use_cache:
2374
 
        _cached_concurrency = concurrency
 
2396
        _cached_local_concurrency = concurrency
2375
2397
    return concurrency
2376
2398
 
2377
2399
 
2423
2445
            else:
2424
2446
                flags |= os.O_WRONLY
2425
2447
            flags |= os.O_CREAT | os.O_APPEND
2426
 
        else: #reading
 
2448
        else:  # reading
2427
2449
            if updating:
2428
2450
                flags |= os.O_RDWR
2429
2451
            else:
2469
2491
 
2470
2492
def find_executable_on_path(name):
2471
2493
    """Finds an executable on the PATH.
2472
 
    
 
2494
 
2473
2495
    On Windows, this will try to append each extension in the PATHEXT
2474
2496
    environment variable to the name, if it cannot be found with the name
2475
2497
    as given.
2476
 
    
 
2498
 
2477
2499
    :param name: The base name of the executable.
2478
2500
    :return: The path to the executable found or None.
2479
2501
    """
2517
2539
            # exists, though not ours
2518
2540
            return False
2519
2541
        else:
2520
 
            mutter("os.kill(%d, 0) failed: %s" % (pid, e))
 
2542
            trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2521
2543
            # Don't really know.
2522
2544
            return False
2523
2545
    else:
2524
2546
        # Exists and our process: not dead.
2525
2547
        return False
2526
2548
 
 
2549
 
2527
2550
if sys.platform == "win32":
2528
2551
    is_local_pid_dead = win32utils.is_local_pid_dead
2529
2552
else:
2536
2559
 
2537
2560
def fdatasync(fileno):
2538
2561
    """Flush file contents to disk if possible.
2539
 
    
 
2562
 
2540
2563
    :param fileno: Integer OS file handle.
2541
2564
    :raises TransportNotPossible: If flushing to disk is not possible.
2542
2565
    """
2556
2579
 
2557
2580
def ensure_empty_directory_exists(path, exception_class):
2558
2581
    """Make sure a local directory exists and is empty.
2559
 
    
 
2582
 
2560
2583
    If it does not exist, it is created.  If it exists and is not empty, an
2561
2584
    instance of exception_class is raised.
2562
2585
    """