• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

twisted / twisted / 1

22 Jul 2021 06:33AM UTC coverage: 80.076% (-2.3%) from 82.403%
1

push

github

web-flow
Merge branch 'trunk' into trunk

27108 of 35905 branches covered (75.5%)

1810 of 1944 new or added lines in 189 files covered. (93.11%)

119955 of 149802 relevant lines covered (80.08%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.44
/src/twisted/python/filepath.py
1
# -*- test-case-name: twisted.test.test_paths -*-
2
# Copyright (c) Twisted Matrix Laboratories.
3
# See LICENSE for details.
4

5
"""
1✔
6
Object-oriented filesystem path representation.
7
"""
8

9

10
import os
1✔
11
import sys
1✔
12
import errno
1✔
13
import base64
1✔
14

15
from os.path import isabs, exists, normpath, abspath, splitext
1✔
16
from os.path import basename, dirname, join as joinpath
1✔
17
from os import listdir, utime, stat
1✔
18

19
from stat import S_ISREG, S_ISDIR, S_IMODE, S_ISBLK, S_ISSOCK
1✔
20
from stat import S_IRUSR, S_IWUSR, S_IXUSR
1✔
21
from stat import S_IRGRP, S_IWGRP, S_IXGRP
1✔
22
from stat import S_IROTH, S_IWOTH, S_IXOTH
1✔
23
from typing import IO, Union, cast
1✔
24

25
from zope.interface import Interface, Attribute, implementer
1✔
26

27
# Please keep this as light as possible on other Twisted imports; many, many
28
# things import this module, and it would be good if it could easily be
29
# modified for inclusion in the standard library.  --glyph
30

31
from twisted.python.compat import comparable, cmp
1✔
32
from twisted.python.runtime import platform
1✔
33
from twisted.python.util import FancyEqMixin
1✔
34
from twisted.python.win32 import ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND
1✔
35
from twisted.python.win32 import ERROR_INVALID_NAME, ERROR_DIRECTORY, O_BINARY
1✔
36

37

38
_CREATE_FLAGS = os.O_EXCL | os.O_CREAT | os.O_RDWR | O_BINARY
1✔
39

40

41
def _stub_islink(path):
1✔
42
    """
43
    Always return C{False} if the operating system does not support symlinks.
44

45
    @param path: A path string.
46
    @type path: L{str}
47

48
    @return: C{False}
49
    @rtype: L{bool}
50
    """
51
    return False
×
52

53

54
islink = getattr(os.path, "islink", _stub_islink)
1✔
55
randomBytes = os.urandom
1✔
56
armor = base64.urlsafe_b64encode
1✔
57

58

59
class IFilePath(Interface):
1✔
60
    """
61
    File path object.
62

63
    A file path represents a location for a file-like-object and can be
64
    organized into a hierarchy; a file path can can children which are
65
    themselves file paths.
66

67
    A file path has a name which unique identifies it in the context of its
68
    parent (if it has one); a file path can not have two children with the same
69
    name.  This name is referred to as the file path's "base name".
70

71
    A series of such names can be used to locate nested children of a file
72
    path; such a series is referred to as the child's "path", relative to the
73
    parent.  In this case, each name in the path is referred to as a "path
74
    segment"; the child's base name is the segment in the path.
75

76
    When representing a file path as a string, a "path separator" is used to
77
    delimit the path segments within the string.  For a file system path, that
78
    would be C{os.sep}.
79

80
    Note that the values of child names may be restricted.  For example, a file
81
    system path will not allow the use of the path separator in a name, and
82
    certain names (e.g. C{"."} and C{".."}) may be reserved or have special
83
    meanings.
84

85
    @since: 12.1
86
    """
87

88
    sep = Attribute("The path separator to use in string representations")
1✔
89

90
    def child(name):
1✔
91
        """
92
        Obtain a direct child of this file path.  The child may or may not
93
        exist.
94

95
        @param name: the name of a child of this path. C{name} must be a direct
96
            child of this path and may not contain a path separator.
97
        @return: the child of this path with the given C{name}.
98
        @raise InsecurePath: if C{name} describes a file path that is not a
99
            direct child of this file path.
100
        """
101

102
    def open(mode="r"):
1✔
103
        """
104
        Opens this file path with the given mode.
105

106
        @return: a file-like object.
107
        @raise Exception: if this file path cannot be opened.
108
        """
109

110
    def changed():
1✔
111
        """
112
        Clear any cached information about the state of this path on disk.
113
        """
114

115
    def getsize():
1✔
116
        """
117
        Retrieve the size of this file in bytes.
118

119
        @return: the size of the file at this file path in bytes.
120
        @raise Exception: if the size cannot be obtained.
121
        """
122

123
    def getModificationTime():
1✔
124
        """
125
        Retrieve the time of last access from this file.
126

127
        @return: a number of seconds from the epoch.
128
        @rtype: L{float}
129
        """
130

131
    def getStatusChangeTime():
1✔
132
        """
133
        Retrieve the time of the last status change for this file.
134

135
        @return: a number of seconds from the epoch.
136
        @rtype: L{float}
137
        """
138

139
    def getAccessTime():
1✔
140
        """
141
        Retrieve the time that this file was last accessed.
142

143
        @return: a number of seconds from the epoch.
144
        @rtype: L{float}
145
        """
146

147
    def exists():
1✔
148
        """
149
        Check if this file path exists.
150

151
        @return: C{True} if the file at this file path exists, C{False}
152
            otherwise.
153
        @rtype: L{bool}
154
        """
155

156
    def isdir():
1✔
157
        """
158
        Check if this file path refers to a directory.
159

160
        @return: C{True} if the file at this file path is a directory, C{False}
161
            otherwise.
162
        """
163

164
    def isfile():
1✔
165
        """
166
        Check if this file path refers to a regular file.
167

168
        @return: C{True} if the file at this file path is a regular file,
169
            C{False} otherwise.
170
        """
171

172
    def children():
1✔
173
        """
174
        List the children of this path object.
175

176
        @return: a sequence of the children of the directory at this file path.
177
        @raise Exception: if the file at this file path is not a directory.
178
        """
179

180
    def basename():
1✔
181
        """
182
        Retrieve the final component of the file path's path (everything
183
        after the final path separator).
184

185
        @return: the base name of this file path.
186
        @rtype: L{str}
187
        """
188

189
    def parent():
1✔
190
        """
191
        A file path for the directory containing the file at this file path.
192
        """
193

194
    def sibling(name):
1✔
195
        """
196
        A file path for the directory containing the file at this file path.
197

198
        @param name: the name of a sibling of this path.  C{name} must be a
199
            direct sibling of this path and may not contain a path separator.
200

201
        @return: a sibling file path of this one.
202
        """
203

204

205
class InsecurePath(Exception):
1✔
206
    """
207
    Error that is raised when the path provided to L{FilePath} is invalid.
208
    """
209

210

211
class LinkError(Exception):
1✔
212
    """
213
    An error with symlinks - either that there are cyclical symlinks or that
214
    symlink are not supported on this platform.
215
    """
216

217

218
class UnlistableError(OSError):
1✔
219
    """
220
    An exception which is used to distinguish between errors which mean 'this
221
    is not a directory you can list' and other, more catastrophic errors.
222

223
    This error will try to look as much like the original error as possible,
224
    while still being catchable as an independent type.
225

226
    @ivar originalException: the actual original exception instance.
227
    """
228

229
    def __init__(self, originalException: OSError):
1✔
230
        """
231
        Create an UnlistableError exception.
232

233
        @param originalException: an instance of OSError.
234
        """
235
        self.__dict__.update(originalException.__dict__)
1✔
236
        self.originalException = originalException
1✔
237

238

239
def _secureEnoughString(path):
1✔
240
    """
241
    Compute a string usable as a new, temporary filename.
242

243
    @param path: The path that the new temporary filename should be able to be
244
        concatenated with.
245

246
    @return: A pseudorandom, 16 byte string for use in secure filenames.
247
    @rtype: the type of C{path}
248
    """
249
    secureishString = armor(randomBytes(16))[:16]
1✔
250
    return _coerceToFilesystemEncoding(path, secureishString)
1✔
251

252

253
class AbstractFilePath:
1✔
254
    """
255
    Abstract implementation of an L{IFilePath}; must be completed by a
256
    subclass.
257

258
    This class primarily exists to provide common implementations of certain
259
    methods in L{IFilePath}. It is *not* a required parent class for
260
    L{IFilePath} implementations, just a useful starting point.
261
    """
262

263
    def getContent(self):
1✔
264
        """
265
        Retrieve the contents of the file at this path.
266

267
        @return: the contents of the file
268
        @rtype: L{bytes}
269
        """
270
        with self.open() as fp:
1✔
271
            return fp.read()
1✔
272

273
    def parents(self):
1✔
274
        """
275
        Retrieve an iterator of all the ancestors of this path.
276

277
        @return: an iterator of all the ancestors of this path, from the most
278
        recent (its immediate parent) to the root of its filesystem.
279
        """
280
        path = self
1✔
281
        parent = path.parent()
1✔
282
        # root.parent() == root, so this means "are we the root"
283
        while path != parent:
1✔
284
            yield parent
1✔
285
            path = parent
1✔
286
            parent = parent.parent()
1✔
287

288
    def children(self):
1✔
289
        """
290
        List the children of this path object.
291

292
        @raise OSError: If an error occurs while listing the directory.  If the
293
        error is 'serious', meaning that the operation failed due to an access
294
        violation, exhaustion of some kind of resource (file descriptors or
295
        memory), OSError or a platform-specific variant will be raised.
296

297
        @raise UnlistableError: If the inability to list the directory is due
298
        to this path not existing or not being a directory, the more specific
299
        OSError subclass L{UnlistableError} is raised instead.
300

301
        @return: an iterable of all currently-existing children of this object.
302
        """
303
        try:
1✔
304
            subnames = self.listdir()
1✔
305
        except OSError as ose:
1✔
306
            # Under Python 3.3 and higher on Windows, WindowsError is an
307
            # alias for OSError.  OSError has a winerror attribute and an
308
            # errno attribute.
309
            #
310
            # The winerror attribute is bound to the Windows error code while
311
            # the errno attribute is bound to a translation of that code to a
312
            # perhaps equivalent POSIX error number.
313
            #
314
            # For further details, refer to:
315
            # https://docs.python.org/3/library/exceptions.html#OSError
316
            if getattr(ose, "winerror", None) in (
1!
317
                ERROR_PATH_NOT_FOUND,
318
                ERROR_FILE_NOT_FOUND,
319
                ERROR_INVALID_NAME,
320
                ERROR_DIRECTORY,
321
            ):
322
                raise UnlistableError(ose)
×
323
            if ose.errno in (errno.ENOENT, errno.ENOTDIR):
1✔
324
                raise UnlistableError(ose)
1✔
325
            # Other possible errors here, according to linux manpages:
326
            # EACCES, EMIFLE, ENFILE, ENOMEM.  None of these seem like the
327
            # sort of thing which should be handled normally. -glyph
328
            raise
1✔
329
        return [self.child(name) for name in subnames]
1✔
330

331
    def walk(self, descend=None):
1✔
332
        """
333
        Yield myself, then each of my children, and each of those children's
334
        children in turn.
335

336
        The optional argument C{descend} is a predicate that takes a FilePath,
337
        and determines whether or not that FilePath is traversed/descended
338
        into.  It will be called with each path for which C{isdir} returns
339
        C{True}.  If C{descend} is not specified, all directories will be
340
        traversed (including symbolic links which refer to directories).
341

342
        @param descend: A one-argument callable that will return True for
343
            FilePaths that should be traversed, False otherwise.
344

345
        @return: a generator yielding FilePath-like objects.
346
        """
347
        yield self
1✔
348
        if self.isdir():
1✔
349
            for c in self.children():
1✔
350
                # we should first see if it's what we want, then we
351
                # can walk through the directory
352
                if descend is None or descend(c):
1✔
353
                    for subc in c.walk(descend):
1✔
354
                        if os.path.realpath(self.path).startswith(
1✔
355
                            os.path.realpath(subc.path)
356
                        ):
357
                            raise LinkError("Cycle in file graph.")
1✔
358
                        yield subc
1✔
359
                else:
360
                    yield c
1✔
361

362
    def sibling(self, path):
1✔
363
        """
364
        Return a L{FilePath} with the same directory as this instance but with
365
        a basename of C{path}.
366

367
        @param path: The basename of the L{FilePath} to return.
368
        @type path: L{str}
369

370
        @return: The sibling path.
371
        @rtype: L{FilePath}
372
        """
373
        return self.parent().child(path)
1✔
374

375
    def descendant(self, segments):
1✔
376
        """
377
        Retrieve a child or child's child of this path.
378

379
        @param segments: A sequence of path segments as L{str} instances.
380

381
        @return: A L{FilePath} constructed by looking up the C{segments[0]}
382
            child of this path, the C{segments[1]} child of that path, and so
383
            on.
384

385
        @since: 10.2
386
        """
387
        path = self
1✔
388
        for name in segments:
1✔
389
            path = path.child(name)
1✔
390
        return path
1✔
391

392
    def segmentsFrom(self, ancestor):
1✔
393
        """
394
        Return a list of segments between a child and its ancestor.
395

396
        For example, in the case of a path X representing /a/b/c/d and a path Y
397
        representing /a/b, C{Y.segmentsFrom(X)} will return C{['c',
398
        'd']}.
399

400
        @param ancestor: an instance of the same class as self, ostensibly an
401
        ancestor of self.
402

403
        @raise ValueError: If the C{ancestor} parameter is not actually an
404
        ancestor, i.e. a path for /x/y/z is passed as an ancestor for /a/b/c/d.
405

406
        @return: a list of strs
407
        """
408
        # this might be an unnecessarily inefficient implementation but it will
409
        # work on win32 and for zipfiles; later I will deterimine if the
410
        # obvious fast implemenation does the right thing too
411
        f = self
1✔
412
        p = f.parent()
1✔
413
        segments = []
1✔
414
        while f != ancestor and p != f:
1✔
415
            segments[0:0] = [f.basename()]
1✔
416
            f = p
1✔
417
            p = p.parent()
1✔
418
        if f == ancestor and segments:
1✔
419
            return segments
1✔
420
        raise ValueError(f"{ancestor!r} not parent of {self!r}")
1✔
421

422
    # new in 8.0
423
    def __hash__(self):
1✔
424
        """
425
        Hash the same as another L{FilePath} with the same path as mine.
426
        """
427
        return hash((self.__class__, self.path))
1✔
428

429
    # pending deprecation in 8.0
430
    def getmtime(self):
1✔
431
        """
432
        Deprecated.  Use getModificationTime instead.
433
        """
434
        return int(self.getModificationTime())
1✔
435

436
    def getatime(self):
1✔
437
        """
438
        Deprecated.  Use getAccessTime instead.
439
        """
440
        return int(self.getAccessTime())
1✔
441

442
    def getctime(self):
1✔
443
        """
444
        Deprecated.  Use getStatusChangeTime instead.
445
        """
446
        return int(self.getStatusChangeTime())
1✔
447

448

449
class RWX(FancyEqMixin):
1✔
450
    """
451
    A class representing read/write/execute permissions for a single user
452
    category (i.e. user/owner, group, or other/world).  Instantiate with
453
    three boolean values: readable? writable? executable?.
454

455
    @type read: C{bool}
456
    @ivar read: Whether permission to read is given
457

458
    @type write: C{bool}
459
    @ivar write: Whether permission to write is given
460

461
    @type execute: C{bool}
462
    @ivar execute: Whether permission to execute is given
463

464
    @since: 11.1
465
    """
466

467
    compareAttributes = ("read", "write", "execute")
1✔
468

469
    def __init__(self, readable, writable, executable):
1✔
470
        self.read = readable
1✔
471
        self.write = writable
1✔
472
        self.execute = executable
1✔
473

474
    def __repr__(self) -> str:
1✔
475
        return "RWX(read={}, write={}, execute={})".format(
1✔
476
            self.read,
477
            self.write,
478
            self.execute,
479
        )
480

481
    def shorthand(self):
1✔
482
        """
483
        Returns a short string representing the permission bits.  Looks like
484
        part of what is printed by command line utilities such as 'ls -l'
485
        (e.g. 'rwx')
486

487
        @return: The shorthand string.
488
        @rtype: L{str}
489
        """
490
        returnval = ["r", "w", "x"]
1✔
491
        i = 0
1✔
492
        for val in (self.read, self.write, self.execute):
1✔
493
            if not val:
1✔
494
                returnval[i] = "-"
1✔
495
            i += 1
1✔
496
        return "".join(returnval)
1✔
497

498

499
class Permissions(FancyEqMixin):
1✔
500
    """
501
    A class representing read/write/execute permissions.  Instantiate with any
502
    portion of the file's mode that includes the permission bits.
503

504
    @type user: L{RWX}
505
    @ivar user: User/Owner permissions
506

507
    @type group: L{RWX}
508
    @ivar group: Group permissions
509

510
    @type other: L{RWX}
511
    @ivar other: Other/World permissions
512

513
    @since: 11.1
514
    """
515

516
    compareAttributes = ("user", "group", "other")
1✔
517

518
    def __init__(self, statModeInt):
1✔
519
        self.user, self.group, self.other = (
1✔
520
            RWX(*(statModeInt & bit > 0 for bit in bitGroup))
521
            for bitGroup in [
522
                [S_IRUSR, S_IWUSR, S_IXUSR],
523
                [S_IRGRP, S_IWGRP, S_IXGRP],
524
                [S_IROTH, S_IWOTH, S_IXOTH],
525
            ]
526
        )
527

528
    def __repr__(self) -> str:
1✔
NEW
529
        return f"[{str(self.user)} | {str(self.group)} | {str(self.other)}]"
×
530

531
    def shorthand(self):
1✔
532
        """
533
        Returns a short string representing the permission bits.  Looks like
534
        what is printed by command line utilities such as 'ls -l'
535
        (e.g. 'rwx-wx--x')
536

537
        @return: The shorthand string.
538
        @rtype: L{str}
539
        """
540
        return "".join([x.shorthand() for x in (self.user, self.group, self.other)])
1✔
541

542

543
def _asFilesystemBytes(path: Union[bytes, str], encoding: str = "") -> bytes:
1✔
544
    """
545
    Return C{path} as a string of L{bytes} suitable for use on this system's
546
    filesystem.
547

548
    @param path: The path to be made suitable.
549
    @type path: L{bytes} or L{unicode}
550
    @param encoding: The encoding to use if coercing to L{bytes}. If none is
551
        given, L{sys.getfilesystemencoding} is used.
552

553
    @return: L{bytes}
554
    """
555
    if isinstance(path, bytes):
1✔
556
        return path
1✔
557
    else:
558
        if not encoding:
1✔
559
            encoding = sys.getfilesystemencoding()
1✔
560
        return path.encode(encoding)
1✔
561

562

563
def _asFilesystemText(path, encoding=None):
1✔
564
    """
565
    Return C{path} as a string of L{unicode} suitable for use on this system's
566
    filesystem.
567

568
    @param path: The path to be made suitable.
569
    @type path: L{bytes} or L{unicode}
570

571
    @param encoding: The encoding to use if coercing to L{unicode}. If none
572
        is given, L{sys.getfilesystemencoding} is used.
573

574
    @return: L{unicode}
575
    """
576
    if type(path) == str:
1✔
577
        return path
1✔
578
    else:
579
        if encoding is None:
1✔
580
            encoding = sys.getfilesystemencoding()
1✔
581
        return path.decode(encoding)
1✔
582

583

584
def _coerceToFilesystemEncoding(path, newpath, encoding=None):
1✔
585
    """
586
    Return a C{newpath} that is suitable for joining to C{path}.
587

588
    @param path: The path that it should be suitable for joining to.
589
    @param newpath: The new portion of the path to be coerced if needed.
590
    @param encoding: If coerced, the encoding that will be used.
591
    """
592
    if type(path) == bytes:
1✔
593
        return _asFilesystemBytes(newpath, encoding=encoding)
1✔
594
    else:
595
        return _asFilesystemText(newpath, encoding=encoding)
1✔
596

597

598
@comparable
1✔
599
@implementer(IFilePath)
1✔
600
class FilePath(AbstractFilePath):
1✔
601
    """
602
    I am a path on the filesystem that only permits 'downwards' access.
603

604
    Instantiate me with a pathname (for example,
605
    FilePath('/home/myuser/public_html')) and I will attempt to only provide
606
    access to files which reside inside that path.  I may be a path to a file,
607
    a directory, or a file which does not exist.
608

609
    The correct way to use me is to instantiate me, and then do ALL filesystem
610
    access through me.  In other words, do not import the 'os' module; if you
611
    need to open a file, call my 'open' method.  If you need to list a
612
    directory, call my 'path' method.
613

614
    Even if you pass me a relative path, I will convert that to an absolute
615
    path internally.
616

617
    The type of C{path} when instantiating decides the mode of the L{FilePath}.
618
    That is, C{FilePath(b"/")} will return a L{bytes} mode L{FilePath}, and
619
    C{FilePath(u"/")} will return a L{unicode} mode L{FilePath}.
620
    C{FilePath("/")} will return a L{bytes} mode L{FilePath} on Python 2, and a
621
    L{unicode} mode L{FilePath} on Python 3.
622

623
    Methods that return a new L{FilePath} use the type of the given subpath to
624
    decide its mode. For example, C{FilePath(b"/").child(u"tmp")} will return a
625
    L{unicode} mode L{FilePath}.
626

627
    @type alwaysCreate: L{bool}
628
    @ivar alwaysCreate: When opening this file, only succeed if the file does
629
        not already exist.
630

631
    @ivar path: The path from which 'downward' traversal is permitted.
632
    """
633

634
    _statinfo = None
1✔
635
    path: Union[bytes, str] = None  # type: ignore[assignment]
1✔
636

637
    def __init__(self, path, alwaysCreate=False):
1✔
638
        """
639
        Convert a path string to an absolute path if necessary and initialize
640
        the L{FilePath} with the result.
641
        """
642
        self.path = abspath(path)
1✔
643
        self.alwaysCreate = alwaysCreate
1✔
644

645
    def __getstate__(self):
1✔
646
        """
647
        Support serialization by discarding cached L{os.stat} results and
648
        returning everything else.
649
        """
650
        d = self.__dict__.copy()
1✔
651
        if "_statinfo" in d:
1!
652
            del d["_statinfo"]
×
653
        return d
1✔
654

655
    @property
1✔
656
    def sep(self):
657
        """
658
        Return a filesystem separator.
659

660
        @return: The native filesystem separator.
661
        @returntype: The same type as C{self.path}.
662
        """
663
        return _coerceToFilesystemEncoding(self.path, os.sep)
1✔
664

665
    def _asBytesPath(self, encoding=None):
1✔
666
        """
667
        Return the path of this L{FilePath} as bytes.
668

669
        @param encoding: The encoding to use if coercing to L{bytes}. If none is
670
            given, L{sys.getfilesystemencoding} is used.
671

672
        @return: L{bytes}
673
        """
674
        return _asFilesystemBytes(self.path, encoding=encoding)
1✔
675

676
    def _asTextPath(self, encoding=None):
1✔
677
        """
678
        Return the path of this L{FilePath} as text.
679

680
        @param encoding: The encoding to use if coercing to L{unicode}. If none
681
            is given, L{sys.getfilesystemencoding} is used.
682

683
        @return: L{unicode}
684
        """
685
        return _asFilesystemText(self.path, encoding=encoding)
1✔
686

687
    def asBytesMode(self, encoding=None):
1✔
688
        """
689
        Return this L{FilePath} in L{bytes}-mode.
690

691
        @param encoding: The encoding to use if coercing to L{bytes}. If none is
692
            given, L{sys.getfilesystemencoding} is used.
693

694
        @return: L{bytes} mode L{FilePath}
695
        """
696
        if type(self.path) == str:
1✔
697
            return self.clonePath(self._asBytesPath(encoding=encoding))
1✔
698
        return self
1✔
699

700
    def asTextMode(self, encoding=None):
1✔
701
        """
702
        Return this L{FilePath} in L{unicode}-mode.
703

704
        @param encoding: The encoding to use if coercing to L{unicode}. If none
705
            is given, L{sys.getfilesystemencoding} is used.
706

707
        @return: L{unicode} mode L{FilePath}
708
        """
709
        if type(self.path) == bytes:
1✔
710
            return self.clonePath(self._asTextPath(encoding=encoding))
1✔
711
        return self
1✔
712

713
    def _getPathAsSameTypeAs(self, pattern):
1✔
714
        """
715
        If C{pattern} is C{bytes}, return L{FilePath.path} as L{bytes}.
716
        Otherwise, return L{FilePath.path} as L{unicode}.
717

718
        @param pattern: The new element of the path that L{FilePath.path} may
719
            need to be coerced to match.
720
        """
721
        if type(pattern) == bytes:
1✔
722
            return self._asBytesPath()
1✔
723
        else:
724
            return self._asTextPath()
1✔
725

726
    def child(self, path):
1✔
727
        """
728
        Create and return a new L{FilePath} representing a path contained by
729
        C{self}.
730

731
        @param path: The base name of the new L{FilePath}.  If this contains
732
            directory separators or parent references it will be rejected.
733
        @type path: L{bytes} or L{unicode}
734

735
        @raise InsecurePath: If the result of combining this path with C{path}
736
            would result in a path which is not a direct child of this path.
737

738
        @return: The child path.
739
        @rtype: L{FilePath} with a mode equal to the type of C{path}.
740
        """
741
        colon = _coerceToFilesystemEncoding(path, ":")
1✔
742
        sep = _coerceToFilesystemEncoding(path, os.sep)
1✔
743
        ourPath = self._getPathAsSameTypeAs(path)
1✔
744

745
        if platform.isWindows() and path.count(colon):
1!
746
            # Catch paths like C:blah that don't have a slash
747
            raise InsecurePath(f"{path!r} contains a colon.")
×
748

749
        norm = normpath(path)
1✔
750
        if sep in norm:
1✔
751
            raise InsecurePath(f"{path!r} contains one or more directory separators")
1✔
752

753
        newpath = abspath(joinpath(ourPath, norm))
1✔
754
        if not newpath.startswith(ourPath):
1✔
755
            raise InsecurePath(f"{newpath!r} is not a child of {ourPath}")
1✔
756
        return self.clonePath(newpath)
1✔
757

758
    def preauthChild(self, path):
1✔
759
        """
760
        Use me if C{path} might have slashes in it, but you know they're safe.
761

762
        @param path: A relative path (ie, a path not starting with C{"/"})
763
            which will be interpreted as a child or descendant of this path.
764
        @type path: L{bytes} or L{unicode}
765

766
        @return: The child path.
767
        @rtype: L{FilePath} with a mode equal to the type of C{path}.
768
        """
769
        ourPath = self._getPathAsSameTypeAs(path)
1✔
770

771
        newpath = abspath(joinpath(ourPath, normpath(path)))
1✔
772
        if not newpath.startswith(ourPath):
1!
773
            raise InsecurePath(f"{newpath} is not a child of {ourPath}")
×
774
        return self.clonePath(newpath)
1✔
775

776
    def childSearchPreauth(self, *paths):
1✔
777
        """
778
        Return my first existing child with a name in C{paths}.
779

780
        C{paths} is expected to be a list of *pre-secured* path fragments;
781
        in most cases this will be specified by a system administrator and not
782
        an arbitrary user.
783

784
        If no appropriately-named children exist, this will return L{None}.
785

786
        @return: L{None} or the child path.
787
        @rtype: L{None} or L{FilePath}
788
        """
789
        for child in paths:
1✔
790
            p = self._getPathAsSameTypeAs(child)
1✔
791
            jp = joinpath(p, child)
1✔
792
            if exists(jp):
1✔
793
                return self.clonePath(jp)
1✔
794

795
    def siblingExtensionSearch(self, *exts):
1✔
796
        """
797
        Attempt to return a path with my name, given multiple possible
798
        extensions.
799

800
        Each extension in C{exts} will be tested and the first path which
801
        exists will be returned.  If no path exists, L{None} will be returned.
802
        If C{''} is in C{exts}, then if the file referred to by this path
803
        exists, C{self} will be returned.
804

805
        The extension '*' has a magic meaning, which means "any path that
806
        begins with C{self.path + '.'} is acceptable".
807
        """
808
        for ext in exts:
1✔
809
            if not ext and self.exists():
1!
810
                return self
×
811

812
            p = self._getPathAsSameTypeAs(ext)
1✔
813
            star = _coerceToFilesystemEncoding(ext, "*")
1✔
814
            dot = _coerceToFilesystemEncoding(ext, ".")
1✔
815

816
            if ext == star:
1✔
817
                basedot = basename(p) + dot
1✔
818
                for fn in listdir(dirname(p)):
1!
819
                    if fn.startswith(basedot):
1!
820
                        return self.clonePath(joinpath(dirname(p), fn))
1✔
821
            p2 = p + ext
1✔
822
            if exists(p2):
1✔
823
                return self.clonePath(p2)
1✔
824

825
    def realpath(self):
1✔
826
        """
827
        Returns the absolute target as a L{FilePath} if self is a link, self
828
        otherwise.
829

830
        The absolute link is the ultimate file or directory the
831
        link refers to (for instance, if the link refers to another link, and
832
        another...).  If the filesystem does not support symlinks, or
833
        if the link is cyclical, raises a L{LinkError}.
834

835
        Behaves like L{os.path.realpath} in that it does not resolve link
836
        names in the middle (ex. /x/y/z, y is a link to w - realpath on z
837
        will return /x/y/z, not /x/w/z).
838

839
        @return: L{FilePath} of the target path.
840
        @rtype: L{FilePath}
841
        @raises LinkError: if links are not supported or links are cyclical.
842
        """
843
        if self.islink():
1✔
844
            result = os.path.realpath(self.path)
1✔
845
            if result == self.path:
1✔
846
                raise LinkError("Cyclical link - will loop forever")
1✔
847
            return self.clonePath(result)
1✔
848
        return self
1✔
849

850
    def siblingExtension(self, ext):
1✔
851
        """
852
        Attempt to return a path with my name, given the extension at C{ext}.
853

854
        @param ext: File-extension to search for.
855
        @type ext: L{bytes} or L{unicode}
856

857
        @return: The sibling path.
858
        @rtype: L{FilePath} with the same mode as the type of C{ext}.
859
        """
860
        ourPath = self._getPathAsSameTypeAs(ext)
1✔
861
        return self.clonePath(ourPath + ext)
1✔
862

863
    def linkTo(self, linkFilePath):
1✔
864
        """
865
        Creates a symlink to self to at the path in the L{FilePath}
866
        C{linkFilePath}.
867

868
        Only works on posix systems due to its dependence on
869
        L{os.symlink}.  Propagates L{OSError}s up from L{os.symlink} if
870
        C{linkFilePath.parent()} does not exist, or C{linkFilePath} already
871
        exists.
872

873
        @param linkFilePath: a FilePath representing the link to be created.
874
        @type linkFilePath: L{FilePath}
875
        """
876
        os.symlink(self.path, linkFilePath.path)
1✔
877

878
    def open(self, mode: str = "r") -> IO[bytes]:
1✔
879
        """
880
        Open this file using C{mode} or for writing if C{alwaysCreate} is
881
        C{True}.
882

883
        In all cases the file is opened in binary mode, so it is not necessary
884
        to include C{"b"} in C{mode}.
885

886
        @param mode: The mode to open the file in.  Default is C{"r"}.
887
        @raises AssertionError: If C{"a"} is included in the mode and
888
            C{alwaysCreate} is C{True}.
889
        @return: An open file-like object.
890
        """
891
        if self.alwaysCreate:
1✔
892
            assert "a" not in mode, (
1✔
893
                "Appending not supported when " "alwaysCreate == True"
894
            )
895
            return self.create()
1✔
896
        # Make sure we open with exactly one "b" in the mode.
897
        mode = mode.replace("b", "")
1✔
898
        return open(self.path, mode + "b")
1✔
899

900
    # stat methods below
901

902
    def restat(self, reraise=True):
1✔
903
        """
904
        Re-calculate cached effects of 'stat'.  To refresh information on this
905
        path after you know the filesystem may have changed, call this method.
906

907
        @param reraise: a boolean.  If true, re-raise exceptions from
908
            L{os.stat}; otherwise, mark this path as not existing, and remove
909
            any cached stat information.
910

911
        @raise Exception: If C{reraise} is C{True} and an exception occurs
912
            while reloading metadata.
913
        """
914
        try:
1✔
915
            self._statinfo = stat(self.path)
1✔
916
        except OSError:
1✔
917
            self._statinfo = 0
1✔
918
            if reraise:
1✔
919
                raise
1✔
920

921
    def changed(self):
1✔
922
        """
923
        Clear any cached information about the state of this path on disk.
924

925
        @since: 10.1.0
926
        """
927
        self._statinfo = None
1✔
928

929
    def chmod(self, mode):
1✔
930
        """
931
        Changes the permissions on self, if possible.  Propagates errors from
932
        L{os.chmod} up.
933

934
        @param mode: integer representing the new permissions desired (same as
935
            the command line chmod)
936
        @type mode: L{int}
937
        """
938
        os.chmod(self.path, mode)
1✔
939

940
    def getsize(self):
1✔
941
        """
942
        Retrieve the size of this file in bytes.
943

944
        @return: The size of the file at this file path in bytes.
945
        @raise Exception: if the size cannot be obtained.
946
        @rtype: L{int}
947
        """
948
        st = self._statinfo
1✔
949
        if not st:
1✔
950
            self.restat()
1✔
951
            st = self._statinfo
1✔
952
        return st.st_size
1✔
953

954
    def getModificationTime(self):
1✔
955
        """
956
        Retrieve the time of last access from this file.
957

958
        @return: a number of seconds from the epoch.
959
        @rtype: L{float}
960
        """
961
        st = self._statinfo
1✔
962
        if not st:
1✔
963
            self.restat()
1✔
964
            st = self._statinfo
1✔
965
        return float(st.st_mtime)
1✔
966

967
    def getStatusChangeTime(self):
1✔
968
        """
969
        Retrieve the time of the last status change for this file.
970

971
        @return: a number of seconds from the epoch.
972
        @rtype: L{float}
973
        """
974
        st = self._statinfo
1✔
975
        if not st:
1✔
976
            self.restat()
1✔
977
            st = self._statinfo
1✔
978
        return float(st.st_ctime)
1✔
979

980
    def getAccessTime(self):
1✔
981
        """
982
        Retrieve the time that this file was last accessed.
983

984
        @return: a number of seconds from the epoch.
985
        @rtype: L{float}
986
        """
987
        st = self._statinfo
1✔
988
        if not st:
1✔
989
            self.restat()
1✔
990
            st = self._statinfo
1✔
991
        return float(st.st_atime)
1✔
992

993
    def getInodeNumber(self):
1✔
994
        """
995
        Retrieve the file serial number, also called inode number, which
996
        distinguishes this file from all other files on the same device.
997

998
        @raise NotImplementedError: if the platform is Windows, since the
999
            inode number would be a dummy value for all files in Windows
1000
        @return: a number representing the file serial number
1001
        @rtype: L{int}
1002
        @since: 11.0
1003
        """
1004
        if platform.isWindows():
1✔
1005
            raise NotImplementedError
1006

1007
        st = self._statinfo
1✔
1008
        if not st:
1!
1009
            self.restat()
1✔
1010
            st = self._statinfo
1✔
1011
        return st.st_ino
1✔
1012

1013
    def getDevice(self):
1✔
1014
        """
1015
        Retrieves the device containing the file.  The inode number and device
1016
        number together uniquely identify the file, but the device number is
1017
        not necessarily consistent across reboots or system crashes.
1018

1019
        @raise NotImplementedError: if the platform is Windows, since the
1020
            device number would be 0 for all partitions on a Windows platform
1021

1022
        @return: a number representing the device
1023
        @rtype: L{int}
1024

1025
        @since: 11.0
1026
        """
1027
        if platform.isWindows():
1✔
1028
            raise NotImplementedError
1029

1030
        st = self._statinfo
1✔
1031
        if not st:
1!
1032
            self.restat()
×
1033
            st = self._statinfo
×
1034
        return st.st_dev
1✔
1035

1036
    def getNumberOfHardLinks(self):
1✔
1037
        """
1038
        Retrieves the number of hard links to the file.
1039

1040
        This count keeps track of how many directories have entries for this
1041
        file. If the count is ever decremented to zero then the file itself is
1042
        discarded as soon as no process still holds it open.  Symbolic links
1043
        are not counted in the total.
1044

1045
        @raise NotImplementedError: if the platform is Windows, since Windows
1046
            doesn't maintain a link count for directories, and L{os.stat} does
1047
            not set C{st_nlink} on Windows anyway.
1048
        @return: the number of hard links to the file
1049
        @rtype: L{int}
1050
        @since: 11.0
1051
        """
1052
        if platform.isWindows():
1✔
1053
            raise NotImplementedError
1054

1055
        st = self._statinfo
1✔
1056
        if not st:
1!
1057
            self.restat()
×
1058
            st = self._statinfo
×
1059
        return st.st_nlink
1✔
1060

1061
    def getUserID(self):
1✔
1062
        """
1063
        Returns the user ID of the file's owner.
1064

1065
        @raise NotImplementedError: if the platform is Windows, since the UID
1066
            is always 0 on Windows
1067
        @return: the user ID of the file's owner
1068
        @rtype: L{int}
1069
        @since: 11.0
1070
        """
1071
        if platform.isWindows():
1✔
1072
            raise NotImplementedError
1073

1074
        st = self._statinfo
1✔
1075
        if not st:
1!
1076
            self.restat()
×
1077
            st = self._statinfo
×
1078
        return st.st_uid
1✔
1079

1080
    def getGroupID(self):
1✔
1081
        """
1082
        Returns the group ID of the file.
1083

1084
        @raise NotImplementedError: if the platform is Windows, since the GID
1085
            is always 0 on windows
1086
        @return: the group ID of the file
1087
        @rtype: L{int}
1088
        @since: 11.0
1089
        """
1090
        if platform.isWindows():
1✔
1091
            raise NotImplementedError
1092

1093
        st = self._statinfo
1✔
1094
        if not st:
1!
1095
            self.restat()
×
1096
            st = self._statinfo
×
1097
        return st.st_gid
1✔
1098

1099
    def getPermissions(self):
1✔
1100
        """
1101
        Returns the permissions of the file.  Should also work on Windows,
1102
        however, those permissions may not be what is expected in Windows.
1103

1104
        @return: the permissions for the file
1105
        @rtype: L{Permissions}
1106
        @since: 11.1
1107
        """
1108
        st = self._statinfo
1✔
1109
        if not st:
1✔
1110
            self.restat()
1✔
1111
            st = self._statinfo
1✔
1112
        return Permissions(S_IMODE(st.st_mode))
1✔
1113

1114
    def exists(self):
1✔
1115
        """
1116
        Check if this L{FilePath} exists.
1117

1118
        @return: C{True} if the stats of C{path} can be retrieved successfully,
1119
            C{False} in the other cases.
1120
        @rtype: L{bool}
1121
        """
1122
        if self._statinfo:
1✔
1123
            return True
1✔
1124
        else:
1125
            self.restat(False)
1✔
1126
            if self._statinfo:
1✔
1127
                return True
1✔
1128
            else:
1129
                return False
1✔
1130

1131
    def isdir(self):
1✔
1132
        """
1133
        Check if this L{FilePath} refers to a directory.
1134

1135
        @return: C{True} if this L{FilePath} refers to a directory, C{False}
1136
            otherwise.
1137
        @rtype: L{bool}
1138
        """
1139
        st = self._statinfo
1✔
1140
        if not st:
1✔
1141
            self.restat(False)
1✔
1142
            st = self._statinfo
1✔
1143
            if not st:
1✔
1144
                return False
1✔
1145
        return S_ISDIR(st.st_mode)
1✔
1146

1147
    def isfile(self):
1✔
1148
        """
1149
        Check if this file path refers to a regular file.
1150

1151
        @return: C{True} if this L{FilePath} points to a regular file (not a
1152
            directory, socket, named pipe, etc), C{False} otherwise.
1153
        @rtype: L{bool}
1154
        """
1155
        st = self._statinfo
1✔
1156
        if not st:
1✔
1157
            self.restat(False)
1✔
1158
            st = self._statinfo
1✔
1159
            if not st:
1✔
1160
                return False
1✔
1161
        return S_ISREG(st.st_mode)
1✔
1162

1163
    def isBlockDevice(self):
1✔
1164
        """
1165
        Returns whether the underlying path is a block device.
1166

1167
        @return: C{True} if it is a block device, C{False} otherwise
1168
        @rtype: L{bool}
1169
        @since: 11.1
1170
        """
1171
        st = self._statinfo
1✔
1172
        if not st:
1!
1173
            self.restat(False)
1✔
1174
            st = self._statinfo
1✔
1175
            if not st:
1!
1176
                return False
×
1177
        return S_ISBLK(st.st_mode)
1✔
1178

1179
    def isSocket(self):
1✔
1180
        """
1181
        Returns whether the underlying path is a socket.
1182

1183
        @return: C{True} if it is a socket, C{False} otherwise
1184
        @rtype: L{bool}
1185
        @since: 11.1
1186
        """
1187
        st = self._statinfo
1✔
1188
        if not st:
1!
1189
            self.restat(False)
×
1190
            st = self._statinfo
×
1191
            if not st:
×
1192
                return False
×
1193
        return S_ISSOCK(st.st_mode)
1✔
1194

1195
    def islink(self):
1✔
1196
        """
1197
        Check if this L{FilePath} points to a symbolic link.
1198

1199
        @return: C{True} if this L{FilePath} points to a symbolic link,
1200
            C{False} otherwise.
1201
        @rtype: L{bool}
1202
        """
1203
        # We can't use cached stat results here, because that is the stat of
1204
        # the destination - (see #1773) which in *every case* but this one is
1205
        # the right thing to use.  We could call lstat here and use that, but
1206
        # it seems unlikely we'd actually save any work that way.  -glyph
1207
        return islink(self.path)
1✔
1208

1209
    def isabs(self):
1✔
1210
        """
1211
        Check if this L{FilePath} refers to an absolute path.
1212

1213
        This always returns C{True}.
1214

1215
        @return: C{True}, always.
1216
        @rtype: L{bool}
1217
        """
1218
        return isabs(self.path)
×
1219

1220
    def listdir(self):
1✔
1221
        """
1222
        List the base names of the direct children of this L{FilePath}.
1223

1224
        @return: A L{list} of L{bytes}/L{unicode} giving the names of the
1225
            contents of the directory this L{FilePath} refers to. These names
1226
            are relative to this L{FilePath}.
1227
        @rtype: L{list}
1228

1229
        @raise OSError: Any exception the platform L{os.listdir} implementation
1230
            may raise.
1231
        """
1232
        return listdir(self.path)
1✔
1233

1234
    def splitext(self):
1✔
1235
        """
1236
        Split the file path into a pair C{(root, ext)} such that
1237
        C{root + ext == path}.
1238

1239
        @return: Tuple where the first item is the filename and second item is
1240
            the file extension. See Python docs for L{os.path.splitext}.
1241
        @rtype: L{tuple}
1242
        """
1243
        return splitext(self.path)
1✔
1244

1245
    def __repr__(self) -> str:
1✔
1246
        return f"FilePath({self.path!r})"
1✔
1247

1248
    def touch(self):
1✔
1249
        """
1250
        Updates the access and last modification times of the file at this
1251
        file path to the current time. Also creates the file if it does not
1252
        already exist.
1253

1254
        @raise Exception: if unable to create or modify the last modification
1255
            time of the file.
1256
        """
1257
        try:
1✔
1258
            self.open("a").close()
1✔
1259
        except OSError:
×
1260
            pass
×
1261
        utime(self.path, None)
1✔
1262

1263
    def remove(self):
1✔
1264
        """
1265
        Removes the file or directory that is represented by self.  If
1266
        C{self.path} is a directory, recursively remove all its children
1267
        before removing the directory. If it's a file or link, just delete it.
1268
        """
1269
        if self.isdir() and not self.islink():
1✔
1270
            for child in self.children():
1✔
1271
                child.remove()
1✔
1272
            os.rmdir(self.path)
1✔
1273
        else:
1274
            os.remove(self.path)
1✔
1275
        self.changed()
1✔
1276

1277
    def makedirs(self, ignoreExistingDirectory=False):
1✔
1278
        """
1279
        Create all directories not yet existing in C{path} segments, using
1280
        L{os.makedirs}.
1281

1282
        @param ignoreExistingDirectory: Don't raise L{OSError} if directory
1283
            already exists.
1284
        @type ignoreExistingDirectory: L{bool}
1285

1286
        @return: L{None}
1287
        """
1288
        try:
1✔
1289
            return os.makedirs(self.path)
1✔
1290
        except OSError as e:
1✔
1291
            if not (
1✔
1292
                e.errno == errno.EEXIST and ignoreExistingDirectory and self.isdir()
1293
            ):
1294
                raise
1✔
1295

1296
    def globChildren(self, pattern):
1✔
1297
        """
1298
        Assuming I am representing a directory, return a list of FilePaths
1299
        representing my children that match the given pattern.
1300

1301
        @param pattern: A glob pattern to use to match child paths.
1302
        @type pattern: L{unicode} or L{bytes}
1303

1304
        @return: A L{list} of matching children.
1305
        @rtype: L{list} of L{FilePath}, with the mode of C{pattern}'s type
1306
        """
1307
        sep = _coerceToFilesystemEncoding(pattern, os.sep)
1✔
1308
        ourPath = self._getPathAsSameTypeAs(pattern)
1✔
1309

1310
        import glob
1✔
1311

1312
        path = ourPath[-1] == sep and ourPath + pattern or sep.join([ourPath, pattern])
1✔
1313
        return [self.clonePath(p) for p in glob.glob(path)]
1✔
1314

1315
    def basename(self):
1✔
1316
        """
1317
        Retrieve the final component of the file path's path (everything
1318
        after the final path separator).
1319

1320
        @return: The final component of the L{FilePath}'s path (Everything
1321
            after the final path separator).
1322
        @rtype: the same type as this L{FilePath}'s C{path} attribute
1323
        """
1324
        return basename(self.path)
1✔
1325

1326
    def dirname(self):
1✔
1327
        """
1328
        Retrieve all of the components of the L{FilePath}'s path except the
1329
        last one (everything up to the final path separator).
1330

1331
        @return: All of the components of the L{FilePath}'s path except the
1332
            last one (everything up to the final path separator).
1333
        @rtype: the same type as this L{FilePath}'s C{path} attribute
1334
        """
1335
        return dirname(self.path)
1✔
1336

1337
    def parent(self):
1✔
1338
        """
1339
        A file path for the directory containing the file at this file path.
1340

1341
        @return: A L{FilePath} representing the path which directly contains
1342
            this L{FilePath}.
1343
        @rtype: L{FilePath}
1344
        """
1345
        return self.clonePath(self.dirname())
1✔
1346

1347
    def setContent(self, content, ext=b".new"):
1✔
1348
        """
1349
        Replace the file at this path with a new file that contains the given
1350
        bytes, trying to avoid data-loss in the meanwhile.
1351

1352
        On UNIX-like platforms, this method does its best to ensure that by the
1353
        time this method returns, either the old contents I{or} the new
1354
        contents of the file will be present at this path for subsequent
1355
        readers regardless of premature device removal, program crash, or power
1356
        loss, making the following assumptions:
1357

1358
            - your filesystem is journaled (i.e. your filesystem will not
1359
              I{itself} lose data due to power loss)
1360

1361
            - your filesystem's C{rename()} is atomic
1362

1363
            - your filesystem will not discard new data while preserving new
1364
              metadata (see U{http://mjg59.livejournal.com/108257.html} for
1365
              more detail)
1366

1367
        On most versions of Windows there is no atomic C{rename()} (see
1368
        U{http://bit.ly/win32-overwrite} for more information), so this method
1369
        is slightly less helpful.  There is a small window where the file at
1370
        this path may be deleted before the new file is moved to replace it:
1371
        however, the new file will be fully written and flushed beforehand so
1372
        in the unlikely event that there is a crash at that point, it should be
1373
        possible for the user to manually recover the new version of their
1374
        data.  In the future, Twisted will support atomic file moves on those
1375
        versions of Windows which I{do} support them: see U{Twisted ticket
1376
        3004<http://twistedmatrix.com/trac/ticket/3004>}.
1377

1378
        This method should be safe for use by multiple concurrent processes,
1379
        but note that it is not easy to predict which process's contents will
1380
        ultimately end up on disk if they invoke this method at close to the
1381
        same time.
1382

1383
        @param content: The desired contents of the file at this path.
1384
        @type content: L{bytes}
1385

1386
        @param ext: An extension to append to the temporary filename used to
1387
            store the bytes while they are being written.  This can be used to
1388
            make sure that temporary files can be identified by their suffix,
1389
            for cleanup in case of crashes.
1390
        @type ext: L{bytes}
1391
        """
1392
        sib = self.temporarySibling(ext)
1✔
1393
        with sib.open("w") as f:
1✔
1394
            f.write(content)
1✔
1395
        if platform.isWindows() and exists(self.path):
1!
1396
            os.unlink(self.path)
×
1397
        os.rename(sib.path, self.asBytesMode().path)
1✔
1398

1399
    def __cmp__(self, other):
1✔
1400
        if not isinstance(other, FilePath):
1!
1401
            return NotImplemented
×
1402
        return cmp(self.path, other.path)
1✔
1403

1404
    def createDirectory(self):
1✔
1405
        """
1406
        Create the directory the L{FilePath} refers to.
1407

1408
        @see: L{makedirs}
1409

1410
        @raise OSError: If the directory cannot be created.
1411
        """
1412
        os.mkdir(self.path)
1✔
1413

1414
    def requireCreate(self, val=1):
1✔
1415
        """
1416
        Sets the C{alwaysCreate} variable.
1417

1418
        @param val: C{True} or C{False}, indicating whether opening this path
1419
            will be required to create the file or not.
1420
        @type val: L{bool}
1421

1422
        @return: L{None}
1423
        """
1424
        self.alwaysCreate = val
1✔
1425

1426
    def create(self) -> IO[bytes]:
1✔
1427
        """
1428
        Exclusively create a file, only if this file previously did not exist.
1429

1430
        @return: A file-like object opened from this path.
1431
        """
1432
        fdint = os.open(self.path, _CREATE_FLAGS)
1✔
1433

1434
        # XXX TODO: 'name' attribute of returned files is not mutable or
1435
        # settable via fdopen, so this file is slightly less functional than the
1436
        # one returned from 'open' by default.  send a patch to Python...
1437

1438
        return cast(IO[bytes], os.fdopen(fdint, "w+b"))
1✔
1439

1440
    def temporarySibling(self, extension=b""):
1✔
1441
        """
1442
        Construct a path referring to a sibling of this path.
1443

1444
        The resulting path will be unpredictable, so that other subprocesses
1445
        should neither accidentally attempt to refer to the same path before it
1446
        is created, nor they should other processes be able to guess its name
1447
        in advance.
1448

1449
        @param extension: A suffix to append to the created filename.  (Note
1450
            that if you want an extension with a '.' you must include the '.'
1451
            yourself.)
1452
        @type extension: L{bytes} or L{unicode}
1453

1454
        @return: a path object with the given extension suffix, C{alwaysCreate}
1455
            set to True.
1456
        @rtype: L{FilePath} with a mode equal to the type of C{extension}
1457
        """
1458
        ourPath = self._getPathAsSameTypeAs(extension)
1✔
1459
        sib = self.sibling(
1✔
1460
            _secureEnoughString(ourPath)
1461
            + self.clonePath(ourPath).basename()
1462
            + extension
1463
        )
1464
        sib.requireCreate()
1✔
1465
        return sib
1✔
1466

1467
    _chunkSize = 2 ** 2 ** 2 ** 2
1✔
1468

1469
    def copyTo(self, destination, followLinks=True):
1✔
1470
        """
1471
        Copies self to destination.
1472

1473
        If self doesn't exist, an OSError is raised.
1474

1475
        If self is a directory, this method copies its children (but not
1476
        itself) recursively to destination - if destination does not exist as a
1477
        directory, this method creates it.  If destination is a file, an
1478
        IOError will be raised.
1479

1480
        If self is a file, this method copies it to destination.  If
1481
        destination is a file, this method overwrites it.  If destination is a
1482
        directory, an IOError will be raised.
1483

1484
        If self is a link (and followLinks is False), self will be copied
1485
        over as a new symlink with the same target as returned by os.readlink.
1486
        That means that if it is absolute, both the old and new symlink will
1487
        link to the same thing.  If it's relative, then perhaps not (and
1488
        it's also possible that this relative link will be broken).
1489

1490
        File/directory permissions and ownership will NOT be copied over.
1491

1492
        If followLinks is True, symlinks are followed so that they're treated
1493
        as their targets.  In other words, if self is a link, the link's target
1494
        will be copied.  If destination is a link, self will be copied to the
1495
        destination's target (the actual destination will be destination's
1496
        target).  Symlinks under self (if self is a directory) will be
1497
        followed and its target's children be copied recursively.
1498

1499
        If followLinks is False, symlinks will be copied over as symlinks.
1500

1501
        @param destination: the destination (a FilePath) to which self
1502
            should be copied
1503
        @param followLinks: whether symlinks in self should be treated as links
1504
            or as their targets
1505
        """
1506
        if self.islink() and not followLinks:
1✔
1507
            os.symlink(os.readlink(self.path), destination.path)
1✔
1508
            return
1✔
1509
        # XXX TODO: *thorough* audit and documentation of the exact desired
1510
        # semantics of this code.  Right now the behavior of existent
1511
        # destination symlinks is convenient, and quite possibly correct, but
1512
        # its security properties need to be explained.
1513
        if self.isdir():
1✔
1514
            if not destination.exists():
1✔
1515
                destination.createDirectory()
1✔
1516
            for child in self.children():
1✔
1517
                destChild = destination.child(child.basename())
1✔
1518
                child.copyTo(destChild, followLinks)
1✔
1519
        elif self.isfile():
1✔
1520
            with destination.open("w") as writefile, self.open() as readfile:
1✔
1521
                while 1:
1✔
1522
                    # XXX TODO: optionally use os.open, os.read and
1523
                    # O_DIRECT and use os.fstatvfs to determine chunk sizes
1524
                    # and make *****sure**** copy is page-atomic; the
1525
                    # following is good enough for 99.9% of everybody and
1526
                    # won't take a week to audit though.
1527
                    chunk = readfile.read(self._chunkSize)
1✔
1528
                    writefile.write(chunk)
1✔
1529
                    if len(chunk) < self._chunkSize:
1!
1530
                        break
1✔
1531
        elif not self.exists():
1✔
1532
            raise OSError(errno.ENOENT, "No such file or directory")
1✔
1533
        else:
1534
            # If you see the following message because you want to copy
1535
            # symlinks, fifos, block devices, character devices, or unix
1536
            # sockets, please feel free to add support to do sensible things in
1537
            # reaction to those types!
1538
            raise NotImplementedError("Only copying of files and directories supported")
1539

1540
    def moveTo(self, destination, followLinks=True):
1✔
1541
        """
1542
        Move self to destination - basically renaming self to whatever
1543
        destination is named.
1544

1545
        If destination is an already-existing directory,
1546
        moves all children to destination if destination is empty.  If
1547
        destination is a non-empty directory, or destination is a file, an
1548
        OSError will be raised.
1549

1550
        If moving between filesystems, self needs to be copied, and everything
1551
        that applies to copyTo applies to moveTo.
1552

1553
        @param destination: the destination (a FilePath) to which self
1554
            should be copied
1555
        @param followLinks: whether symlinks in self should be treated as links
1556
            or as their targets (only applicable when moving between
1557
            filesystems)
1558
        """
1559
        try:
1✔
1560
            os.rename(self._getPathAsSameTypeAs(destination.path), destination.path)
1✔
1561
        except OSError as ose:
1✔
1562
            if ose.errno == errno.EXDEV:
1✔
1563
                # man 2 rename, ubuntu linux 5.10 "breezy":
1564

1565
                #   oldpath and newpath are not on the same mounted filesystem.
1566
                #   (Linux permits a filesystem to be mounted at multiple
1567
                #   points, but rename(2) does not work across different mount
1568
                #   points, even if the same filesystem is mounted on both.)
1569

1570
                # that means it's time to copy trees of directories!
1571
                secsib = destination.temporarySibling()
1✔
1572
                self.copyTo(secsib, followLinks)  # slow
1✔
1573
                secsib.moveTo(destination, followLinks)  # visible
1✔
1574

1575
                # done creating new stuff.  let's clean me up.
1576
                mysecsib = self.temporarySibling()
1✔
1577
                self.moveTo(mysecsib, followLinks)  # visible
1✔
1578
                mysecsib.remove()  # slow
1✔
1579
            else:
1580
                raise
1✔
1581
        else:
1582
            self.changed()
1✔
1583
            destination.changed()
1✔
1584

1585

1586
FilePath.clonePath = FilePath  # type: ignore[attr-defined]
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc