1 # -*- test-case-name: twisted.test.test_paths -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Object-oriented filesystem path representation.
14 from os.path import isabs, exists, normpath, abspath, splitext
15 from os.path import basename, dirname
16 from os.path import join as joinpath
17 from os import sep as slash
18 from os import listdir, utime, stat
20 from stat import S_ISREG, S_ISDIR, S_IMODE, S_ISBLK, S_ISSOCK
21 from stat import S_IRUSR, S_IWUSR, S_IXUSR
22 from stat import S_IRGRP, S_IWGRP, S_IXGRP
23 from stat import S_IROTH, S_IWOTH, S_IXOTH
26 # Please keep this as light as possible on other Twisted imports; many, many
27 # things import this module, and it would be good if it could easily be
28 # modified for inclusion in the standard library. --glyph
30 from twisted.python.runtime import platform
31 from twisted.python.hashlib import sha1
33 from twisted.python.win32 import ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND
34 from twisted.python.win32 import ERROR_INVALID_NAME, ERROR_DIRECTORY, O_BINARY
35 from twisted.python.win32 import WindowsError
37 from twisted.python.util import FancyEqMixin
39 from zope.interface import Interface, Attribute, implements
41 _CREATE_FLAGS = (os.O_EXCL |
47 def _stub_islink(path):
49 Always return 'false' if the operating system does not support symlinks.
51 @param path: a path string.
60 Provide random data in versions of Python prior to 2.4. This is an
61 effectively compatible replacement for 'os.urandom'.
64 @param n: the number of bytes of data to return
65 @return: C{n} bytes of random data.
68 randomData = [random.randrange(256) for n in xrange(n)]
69 return ''.join(map(chr, randomData))
74 ASCII-armor for random data. This uses a hex encoding, although we will
75 prefer url-safe base64 encoding for features in this module if it is
78 return s.encode('hex')
80 islink = getattr(os.path, 'islink', _stub_islink)
81 randomBytes = getattr(os, 'urandom', _stub_urandom)
82 armor = getattr(base64, 'urlsafe_b64encode', _stub_armor)
84 class IFilePath(Interface):
88 A file path represents a location for a file-like-object and can be
89 organized into a hierarchy; a file path can can children which are
90 themselves file paths.
92 A file path has a name which unique identifies it in the context of its
93 parent (if it has one); a file path can not have two children with the same
94 name. This name is referred to as the file path's "base name".
96 A series of such names can be used to locate nested children of a file path;
97 such a series is referred to as the child's "path", relative to the parent.
98 In this case, each name in the path is referred to as a "path segment"; the
99 child's base name is the segment in the path.
101 When representing a file path as a string, a "path separator" is used to
102 delimit the path segments within the string. For a file system path, that
105 Note that the values of child names may be restricted. For example, a file
106 system path will not allow the use of the path separator in a name, and
107 certain names (eg. C{"."} and C{".."}) may be reserved or have special
112 sep = Attribute("The path separator to use in string representations")
116 Obtain a direct child of this file path. The child may or may not
119 @param name: the name of a child of this path. C{name} must be a direct
120 child of this path and may not contain a path separator.
121 @return: the child of this path with the given C{name}.
122 @raise InsecurePath: if C{name} describes a file path that is not a
123 direct child of this file path.
128 Opens this file path with the given mode.
129 @return: a file-like-object.
130 @raise Exception: if this file path cannot be opened.
135 Clear any cached information about the state of this path on disk.
140 @return: the size of the file at this file path in bytes.
141 @raise Exception: if the size cannot be obtained.
144 def getModificationTime():
146 Retrieve the time of last access from this file.
148 @return: a number of seconds from the epoch.
152 def getStatusChangeTime():
154 Retrieve the time of the last status change for this file.
156 @return: a number of seconds from the epoch.
162 Retrieve the time that this file was last accessed.
164 @return: a number of seconds from the epoch.
170 @return: C{True} if the file at this file path exists, C{False}
176 @return: C{True} if the file at this file path is a directory, C{False}
182 @return: C{True} if the file at this file path is a regular file,
188 @return: a sequence of the children of the directory at this file path.
189 @raise Exception: if the file at this file path is not a directory.
194 @return: the base name of this file path.
199 A file path for the directory containing the file at this file path.
204 A file path for the directory containing the file at this file path.
205 @param name: the name of a sibling of this path. C{name} must be a direct
206 sibling of this path and may not contain a path separator.
208 @return: a sibling file path of this one.
211 class InsecurePath(Exception):
213 Error that is raised when the path provided to FilePath is invalid.
218 class LinkError(Exception):
220 An error with symlinks - either that there are cyclical symlinks or that
221 symlink are not supported on this platform.
226 class UnlistableError(OSError):
228 An exception which is used to distinguish between errors which mean 'this
229 is not a directory you can list' and other, more catastrophic errors.
231 This error will try to look as much like the original error as possible,
232 while still being catchable as an independent type.
234 @ivar originalException: the actual original exception instance, either an
235 L{OSError} or a L{WindowsError}.
237 def __init__(self, originalException):
239 Create an UnlistableError exception.
241 @param originalException: an instance of OSError.
243 self.__dict__.update(originalException.__dict__)
244 self.originalException = originalException
248 class _WindowsUnlistableError(UnlistableError, WindowsError):
250 This exception is raised on Windows, for compatibility with previous
251 releases of FilePath where unportable programs may have done "except
252 WindowsError:" around a call to children().
254 It is private because all application code may portably catch
255 L{UnlistableError} instead.
260 def _secureEnoughString():
262 Create a pseudorandom, 16-character string for use in secure filenames.
264 return armor(sha1(randomBytes(64)).digest())[:16]
268 class AbstractFilePath(object):
270 Abstract implementation of an IFilePath; must be completed by a subclass.
272 This class primarily exists to provide common implementations of certain
273 methods in IFilePath. It is *not* a required parent class for IFilePath
274 implementations, just a useful starting point.
277 def getContent(self):
287 @return: an iterator of all the ancestors of this path, from the most
288 recent (its immediate parent) to the root of its filesystem.
291 parent = path.parent()
292 # root.parent() == root, so this means "are we the root"
293 while path != parent:
296 parent = parent.parent()
301 List the children of this path object.
303 @raise OSError: If an error occurs while listing the directory. If the
304 error is 'serious', meaning that the operation failed due to an access
305 violation, exhaustion of some kind of resource (file descriptors or
306 memory), OSError or a platform-specific variant will be raised.
308 @raise UnlistableError: If the inability to list the directory is due
309 to this path not existing or not being a directory, the more specific
310 OSError subclass L{UnlistableError} is raised instead.
312 @return: an iterable of all currently-existing children of this object
313 accessible with L{_PathHelper.child}.
316 subnames = self.listdir()
317 except WindowsError, winErrObj:
318 # WindowsError is an OSError subclass, so if not for this clause
319 # the OSError clause below would be handling these. Windows error
320 # codes aren't the same as POSIX error codes, so we need to handle
323 # Under Python 2.5 on Windows, WindowsError has a winerror
324 # attribute and an errno attribute. The winerror attribute is
325 # bound to the Windows error code while the errno attribute is
326 # bound to a translation of that code to a perhaps equivalent POSIX
329 # Under Python 2.4 on Windows, WindowsError only has an errno
330 # attribute. It is bound to the Windows error code.
332 # For simplicity of code and to keep the number of paths through
333 # this suite minimal, we grab the Windows error code under either
336 # Furthermore, attempting to use os.listdir on a non-existent path
337 # in Python 2.4 will result in a Windows error code of
338 # ERROR_PATH_NOT_FOUND. However, in Python 2.5,
339 # ERROR_FILE_NOT_FOUND results instead. -exarkun
340 winerror = getattr(winErrObj, 'winerror', winErrObj.errno)
341 if winerror not in (ERROR_PATH_NOT_FOUND,
342 ERROR_FILE_NOT_FOUND,
346 raise _WindowsUnlistableError(winErrObj)
348 if ose.errno not in (errno.ENOENT, errno.ENOTDIR):
349 # Other possible errors here, according to linux manpages:
350 # EACCES, EMIFLE, ENFILE, ENOMEM. None of these seem like the
351 # sort of thing which should be handled normally. -glyph
353 raise UnlistableError(ose)
354 return map(self.child, subnames)
356 def walk(self, descend=None):
358 Yield myself, then each of my children, and each of those children's
359 children in turn. The optional argument C{descend} is a predicate that
360 takes a FilePath, and determines whether or not that FilePath is
361 traversed/descended into. It will be called with each path for which
362 C{isdir} returns C{True}. If C{descend} is not specified, all
363 directories will be traversed (including symbolic links which refer to
366 @param descend: A one-argument callable that will return True for
367 FilePaths that should be traversed, False otherwise.
369 @return: a generator yielding FilePath-like objects.
373 for c in self.children():
374 # we should first see if it's what we want, then we
375 # can walk through the directory
376 if (descend is None or descend(c)):
377 for subc in c.walk(descend):
378 if os.path.realpath(self.path).startswith(
379 os.path.realpath(subc.path)):
380 raise LinkError("Cycle in file graph.")
386 def sibling(self, path):
388 Return a L{FilePath} with the same directory as this instance but with a
391 @param path: The basename of the L{FilePath} to return.
396 return self.parent().child(path)
399 def descendant(self, segments):
401 Retrieve a child or child's child of this path.
403 @param segments: A sequence of path segments as C{str} instances.
405 @return: A L{FilePath} constructed by looking up the C{segments[0]}
406 child of this path, the C{segments[1]} child of that path, and so
412 for name in segments:
413 path = path.child(name)
417 def segmentsFrom(self, ancestor):
419 Return a list of segments between a child and its ancestor.
421 For example, in the case of a path X representing /a/b/c/d and a path Y
422 representing /a/b, C{Y.segmentsFrom(X)} will return C{['c',
425 @param ancestor: an instance of the same class as self, ostensibly an
428 @raise: ValueError if the 'ancestor' parameter is not actually an
429 ancestor, i.e. a path for /x/y/z is passed as an ancestor for /a/b/c/d.
431 @return: a list of strs
433 # this might be an unnecessarily inefficient implementation but it will
434 # work on win32 and for zipfiles; later I will deterimine if the
435 # obvious fast implemenation does the right thing too
439 while f != ancestor and p != f:
440 segments[0:0] = [f.basename()]
443 if f == ancestor and segments:
445 raise ValueError("%r not parent of %r" % (ancestor, self))
451 Hash the same as another FilePath with the same path as mine.
453 return hash((self.__class__, self.path))
456 # pending deprecation in 8.0
459 Deprecated. Use getModificationTime instead.
461 return int(self.getModificationTime())
466 Deprecated. Use getAccessTime instead.
468 return int(self.getAccessTime())
473 Deprecated. Use getStatusChangeTime instead.
475 return int(self.getStatusChangeTime())
479 class RWX(FancyEqMixin, object):
481 A class representing read/write/execute permissions for a single user
482 category (i.e. user/owner, group, or other/world). Instantiate with
483 three boolean values: readable? writable? executable?.
486 @ivar read: Whether permission to read is given
489 @ivar write: Whether permission to write is given
491 @type execute: C{bool}
492 @ivar execute: Whether permission to execute is given
496 compareAttributes = ('read', 'write', 'execute')
497 def __init__(self, readable, writable, executable):
499 self.write = writable
500 self.execute = executable
504 return "RWX(read=%s, write=%s, execute=%s)" % (
505 self.read, self.write, self.execute)
510 Returns a short string representing the permission bits. Looks like
511 part of what is printed by command line utilities such as 'ls -l'
514 returnval = ['r', 'w', 'x']
516 for val in (self.read, self.write, self.execute):
520 return ''.join(returnval)
524 class Permissions(FancyEqMixin, object):
526 A class representing read/write/execute permissions. Instantiate with any
527 portion of the file's mode that includes the permission bits.
530 @ivar user: User/Owner permissions
533 @ivar group: Group permissions
536 @ivar other: Other/World permissions
541 compareAttributes = ('user', 'group', 'other')
543 def __init__(self, statModeInt):
544 self.user, self.group, self.other = (
545 [RWX(*[statModeInt & bit > 0 for bit in bitGroup]) for bitGroup in
546 [[S_IRUSR, S_IWUSR, S_IXUSR],
547 [S_IRGRP, S_IWGRP, S_IXGRP],
548 [S_IROTH, S_IWOTH, S_IXOTH]]]
553 return "[%s | %s | %s]" % (
554 str(self.user), str(self.group), str(self.other))
559 Returns a short string representing the permission bits. Looks like
560 what is printed by command line utilities such as 'ls -l'
564 [x.shorthand() for x in (self.user, self.group, self.other)])
568 class FilePath(AbstractFilePath):
570 I am a path on the filesystem that only permits 'downwards' access.
572 Instantiate me with a pathname (for example,
573 FilePath('/home/myuser/public_html')) and I will attempt to only provide
574 access to files which reside inside that path. I may be a path to a file,
575 a directory, or a file which does not exist.
577 The correct way to use me is to instantiate me, and then do ALL filesystem
578 access through me. In other words, do not import the 'os' module; if you
579 need to open a file, call my 'open' method. If you need to list a
580 directory, call my 'path' method.
582 Even if you pass me a relative path, I will convert that to an absolute
585 Note: although time-related methods do return floating-point results, they
586 may still be only second resolution depending on the platform and the last
587 value passed to L{os.stat_float_times}. If you want greater-than-second
588 precision, call C{os.stat_float_times(True)}, or use Python 2.5.
589 Greater-than-second precision is only available in Windows on Python2.5 and
592 @type alwaysCreate: C{bool}
593 @ivar alwaysCreate: When opening this file, only succeed if the file does
597 @ivar path: The path from which 'downward' traversal is permitted.
599 @ivar statinfo: The currently cached status information about the file on
600 the filesystem that this L{FilePath} points to. This attribute is
601 C{None} if the file is in an indeterminate state (either this
602 L{FilePath} has not yet had cause to call C{stat()} yet or
603 L{FilePath.changed} indicated that new information is required), 0 if
604 C{stat()} was called and returned an error (i.e. the path did not exist
605 when C{stat()} was called), or a C{stat_result} object that describes
606 the last known status of the underlying file (or directory, as the case
607 may be). Trust me when I tell you that you do not want to use this
608 attribute. Instead, use the methods on L{FilePath} which give you
609 information about it, like C{getsize()}, C{isdir()},
610 C{getModificationTime()}, and so on.
611 @type statinfo: C{int} or L{types.NoneType} or L{os.stat_result}
614 implements(IFilePath)
621 def __init__(self, path, alwaysCreate=False):
623 Convert a path string to an absolute path if necessary and initialize
624 the L{FilePath} with the result.
626 self.path = abspath(path)
627 self.alwaysCreate = alwaysCreate
629 def __getstate__(self):
631 Support serialization by discarding cached L{os.stat} results and
632 returning everything else.
634 d = self.__dict__.copy()
635 if d.has_key('statinfo'):
640 def child(self, path):
642 Create and return a new L{FilePath} representing a path contained by
645 @param path: The base name of the new L{FilePath}. If this contains
646 directory separators or parent references it will be rejected.
649 @raise InsecurePath: If the result of combining this path with C{path}
650 would result in a path which is not a direct child of this path.
652 if platform.isWindows() and path.count(":"):
653 # Catch paths like C:blah that don't have a slash
654 raise InsecurePath("%r contains a colon." % (path,))
655 norm = normpath(path)
657 raise InsecurePath("%r contains one or more directory separators" % (path,))
658 newpath = abspath(joinpath(self.path, norm))
659 if not newpath.startswith(self.path):
660 raise InsecurePath("%r is not a child of %s" % (newpath, self.path))
661 return self.clonePath(newpath)
664 def preauthChild(self, path):
666 Use me if `path' might have slashes in it, but you know they're safe.
668 (NOT slashes at the beginning. It still needs to be a _child_).
670 newpath = abspath(joinpath(self.path, normpath(path)))
671 if not newpath.startswith(self.path):
672 raise InsecurePath("%s is not a child of %s" % (newpath, self.path))
673 return self.clonePath(newpath)
675 def childSearchPreauth(self, *paths):
676 """Return my first existing child with a name in 'paths'.
678 paths is expected to be a list of *pre-secured* path fragments; in most
679 cases this will be specified by a system administrator and not an
682 If no appropriately-named children exist, this will return None.
686 jp = joinpath(p, child)
688 return self.clonePath(jp)
690 def siblingExtensionSearch(self, *exts):
691 """Attempt to return a path with my name, given multiple possible
694 Each extension in exts will be tested and the first path which exists
695 will be returned. If no path exists, None will be returned. If '' is
696 in exts, then if the file referred to by this path exists, 'self' will
699 The extension '*' has a magic meaning, which means "any path that
700 begins with self.path+'.' is acceptable".
704 if not ext and self.exists():
707 basedot = basename(p)+'.'
708 for fn in listdir(dirname(p)):
709 if fn.startswith(basedot):
710 return self.clonePath(joinpath(dirname(p), fn))
713 return self.clonePath(p2)
718 Returns the absolute target as a FilePath if self is a link, self
719 otherwise. The absolute link is the ultimate file or directory the
720 link refers to (for instance, if the link refers to another link, and
721 another...). If the filesystem does not support symlinks, or
722 if the link is cyclical, raises a LinkError.
724 Behaves like L{os.path.realpath} in that it does not resolve link
725 names in the middle (ex. /x/y/z, y is a link to w - realpath on z
726 will return /x/y/z, not /x/w/z).
728 @return: FilePath of the target path
729 @raises LinkError: if links are not supported or links are cyclical.
732 result = os.path.realpath(self.path)
733 if result == self.path:
734 raise LinkError("Cyclical link - will loop forever")
735 return self.clonePath(result)
739 def siblingExtension(self, ext):
740 return self.clonePath(self.path+ext)
743 def linkTo(self, linkFilePath):
745 Creates a symlink to self to at the path in the L{FilePath}
746 C{linkFilePath}. Only works on posix systems due to its dependence on
747 C{os.symlink}. Propagates C{OSError}s up from C{os.symlink} if
748 C{linkFilePath.parent()} does not exist, or C{linkFilePath} already
751 @param linkFilePath: a FilePath representing the link to be created
752 @type linkFilePath: L{FilePath}
754 os.symlink(self.path, linkFilePath.path)
757 def open(self, mode='r'):
759 Open this file using C{mode} or for writing if C{alwaysCreate} is
762 In all cases the file is opened in binary mode, so it is not necessary
763 to include C{b} in C{mode}.
765 @param mode: The mode to open the file in. Default is C{r}.
767 @raises AssertionError: If C{a} is included in the mode and
768 C{alwaysCreate} is C{True}.
770 @return: An open C{file} object.
772 if self.alwaysCreate:
773 assert 'a' not in mode, ("Appending not supported when "
774 "alwaysCreate == True")
776 # This hack is necessary because of a bug in Python 2.7 on Windows:
777 # http://bugs.python.org/issue7686
778 mode = mode.replace('b', '')
779 return open(self.path, mode + 'b')
783 def restat(self, reraise=True):
785 Re-calculate cached effects of 'stat'. To refresh information on this path
786 after you know the filesystem may have changed, call this method.
788 @param reraise: a boolean. If true, re-raise exceptions from
789 L{os.stat}; otherwise, mark this path as not existing, and remove any
790 cached stat information.
792 @raise Exception: is C{reraise} is C{True} and an exception occurs while
796 self.statinfo = stat(self.path)
805 Clear any cached information about the state of this path on disk.
812 def chmod(self, mode):
814 Changes the permissions on self, if possible. Propagates errors from
817 @param mode: integer representing the new permissions desired (same as
818 the command line chmod)
821 os.chmod(self.path, mode)
832 def getModificationTime(self):
834 Retrieve the time of last access from this file.
836 @return: a number of seconds from the epoch.
843 return float(st.st_mtime)
846 def getStatusChangeTime(self):
848 Retrieve the time of the last status change for this file.
850 @return: a number of seconds from the epoch.
857 return float(st.st_ctime)
860 def getAccessTime(self):
862 Retrieve the time that this file was last accessed.
864 @return: a number of seconds from the epoch.
871 return float(st.st_atime)
874 def getInodeNumber(self):
876 Retrieve the file serial number, also called inode number, which
877 distinguishes this file from all other files on the same device.
879 @raise: NotImplementedError if the platform is Windows, since the
880 inode number would be a dummy value for all files in Windows
881 @return: a number representing the file serial number
885 if platform.isWindows():
886 raise NotImplementedError
892 return long(st.st_ino)
897 Retrieves the device containing the file. The inode number and device
898 number together uniquely identify the file, but the device number is
899 not necessarily consistent across reboots or system crashes.
901 @raise: NotImplementedError if the platform is Windows, since the
902 device number would be 0 for all partitions on a Windows
904 @return: a number representing the device
908 if platform.isWindows():
909 raise NotImplementedError
915 return long(st.st_dev)
918 def getNumberOfHardLinks(self):
920 Retrieves the number of hard links to the file. This count keeps
921 track of how many directories have entries for this file. If the
922 count is ever decremented to zero then the file itself is discarded
923 as soon as no process still holds it open. Symbolic links are not
924 counted in the total.
926 @raise: NotImplementedError if the platform is Windows, since Windows
927 doesn't maintain a link count for directories, and os.stat
928 does not set st_nlink on Windows anyway.
929 @return: the number of hard links to the file
933 if platform.isWindows():
934 raise NotImplementedError
940 return int(st.st_nlink)
945 Returns the user ID of the file's owner.
947 @raise: NotImplementedError if the platform is Windows, since the UID
948 is always 0 on Windows
949 @return: the user ID of the file's owner
953 if platform.isWindows():
954 raise NotImplementedError
960 return int(st.st_uid)
963 def getGroupID(self):
965 Returns the group ID of the file.
967 @raise: NotImplementedError if the platform is Windows, since the GID
968 is always 0 on windows
969 @return: the group ID of the file
973 if platform.isWindows():
974 raise NotImplementedError
980 return int(st.st_gid)
983 def getPermissions(self):
985 Returns the permissions of the file. Should also work on Windows,
986 however, those permissions may not what is expected in Windows.
988 @return: the permissions for the file
989 @rtype: L{Permissions}
996 return Permissions(S_IMODE(st.st_mode))
1001 Check if this L{FilePath} exists.
1003 @return: C{True} if the stats of C{path} can be retrieved successfully,
1004 C{False} in the other cases.
1019 @return: C{True} if this L{FilePath} refers to a directory, C{False}
1028 return S_ISDIR(st.st_mode)
1033 @return: C{True} if this L{FilePath} points to a regular file (not a
1034 directory, socket, named pipe, etc), C{False} otherwise.
1042 return S_ISREG(st.st_mode)
1045 def isBlockDevice(self):
1047 Returns whether the underlying path is a block device.
1049 @return: C{True} if it is a block device, C{False} otherwise
1059 return S_ISBLK(st.st_mode)
1064 Returns whether the underlying path is a socket.
1066 @return: C{True} if it is a socket, C{False} otherwise
1076 return S_ISSOCK(st.st_mode)
1081 @return: C{True} if this L{FilePath} points to a symbolic link.
1083 # We can't use cached stat results here, because that is the stat of
1084 # the destination - (see #1773) which in *every case* but this one is
1085 # the right thing to use. We could call lstat here and use that, but
1086 # it seems unlikely we'd actually save any work that way. -glyph
1087 return islink(self.path)
1092 @return: C{True}, always.
1094 return isabs(self.path)
1099 List the base names of the direct children of this L{FilePath}.
1101 @return: a C{list} of C{str} giving the names of the contents of the
1102 directory this L{FilePath} refers to. These names are relative to
1105 @raise: Anything the platform C{os.listdir} implementation might raise
1106 (typically OSError).
1108 return listdir(self.path)
1113 @return: tuple where the first item is the filename and second item is
1114 the file extension. See Python docs for C{os.path.splitext}
1116 return splitext(self.path)
1120 return 'FilePath(%r)' % (self.path,)
1125 Updates the access and last modification times of the file at this
1126 file path to the current time. Also creates the file if it does not
1129 @raise Exception: if unable to create or modify the last modification
1133 self.open('a').close()
1136 utime(self.path, None)
1141 Removes the file or directory that is represented by self. If
1142 C{self.path} is a directory, recursively remove all its children
1143 before removing the directory. If it's a file or link, just delete it.
1145 if self.isdir() and not self.islink():
1146 for child in self.children():
1150 os.remove(self.path)
1156 Create all directories not yet existing in C{path} segments, using
1159 return os.makedirs(self.path)
1162 def globChildren(self, pattern):
1164 Assuming I am representing a directory, return a list of
1165 FilePaths representing my children that match the given
1169 path = self.path[-1] == '/' and self.path + pattern or self.sep.join([self.path, pattern])
1170 return map(self.clonePath, glob.glob(path))
1175 @return: The final component of the L{FilePath}'s path (Everything after
1176 the final path separator).
1179 return basename(self.path)
1184 @return: All of the components of the L{FilePath}'s path except the last
1185 one (everything up to the final path separator).
1188 return dirname(self.path)
1193 @return: A L{FilePath} representing the path which directly contains
1196 return self.clonePath(self.dirname())
1199 def setContent(self, content, ext='.new'):
1201 Replace the file at this path with a new file that contains the given
1202 bytes, trying to avoid data-loss in the meanwhile.
1204 On UNIX-like platforms, this method does its best to ensure that by the
1205 time this method returns, either the old contents I{or} the new contents
1206 of the file will be present at this path for subsequent readers
1207 regardless of premature device removal, program crash, or power loss,
1208 making the following assumptions:
1210 - your filesystem is journaled (i.e. your filesystem will not
1211 I{itself} lose data due to power loss)
1213 - your filesystem's C{rename()} is atomic
1215 - your filesystem will not discard new data while preserving new
1216 metadata (see U{http://mjg59.livejournal.com/108257.html} for more
1219 On most versions of Windows there is no atomic C{rename()} (see
1220 U{http://bit.ly/win32-overwrite} for more information), so this method
1221 is slightly less helpful. There is a small window where the file at
1222 this path may be deleted before the new file is moved to replace it:
1223 however, the new file will be fully written and flushed beforehand so in
1224 the unlikely event that there is a crash at that point, it should be
1225 possible for the user to manually recover the new version of their data.
1226 In the future, Twisted will support atomic file moves on those versions
1227 of Windows which I{do} support them: see U{Twisted ticket
1228 3004<http://twistedmatrix.com/trac/ticket/3004>}.
1230 This method should be safe for use by multiple concurrent processes, but
1231 note that it is not easy to predict which process's contents will
1232 ultimately end up on disk if they invoke this method at close to the
1235 @param content: The desired contents of the file at this path.
1237 @type content: L{str}
1239 @param ext: An extension to append to the temporary filename used to
1240 store the bytes while they are being written. This can be used to
1241 make sure that temporary files can be identified by their suffix,
1242 for cleanup in case of crashes.
1246 sib = self.temporarySibling(ext)
1252 if platform.isWindows() and exists(self.path):
1253 os.unlink(self.path)
1254 os.rename(sib.path, self.path)
1259 def __cmp__(self, other):
1260 if not isinstance(other, FilePath):
1261 return NotImplemented
1262 return cmp(self.path, other.path)
1265 def createDirectory(self):
1267 Create the directory the L{FilePath} refers to.
1271 @raise OSError: If the directory cannot be created.
1276 def requireCreate(self, val=1):
1277 self.alwaysCreate = val
1282 Exclusively create a file, only if this file previously did not exist.
1284 fdint = os.open(self.path, _CREATE_FLAGS)
1286 # XXX TODO: 'name' attribute of returned files is not mutable or
1287 # settable via fdopen, so this file is slighly less functional than the
1288 # one returned from 'open' by default. send a patch to Python...
1290 return os.fdopen(fdint, 'w+b')
1293 def temporarySibling(self, extension=""):
1295 Construct a path referring to a sibling of this path.
1297 The resulting path will be unpredictable, so that other subprocesses
1298 should neither accidentally attempt to refer to the same path before it
1299 is created, nor they should other processes be able to guess its name in
1302 @param extension: A suffix to append to the created filename. (Note
1303 that if you want an extension with a '.' you must include the '.'
1306 @type extension: C{str}
1308 @return: a path object with the given extension suffix, C{alwaysCreate}
1313 sib = self.sibling(_secureEnoughString() + self.basename() + extension)
1318 _chunkSize = 2 ** 2 ** 2 ** 2
1320 def copyTo(self, destination, followLinks=True):
1322 Copies self to destination.
1324 If self doesn't exist, an OSError is raised.
1326 If self is a directory, this method copies its children (but not
1327 itself) recursively to destination - if destination does not exist as a
1328 directory, this method creates it. If destination is a file, an
1329 IOError will be raised.
1331 If self is a file, this method copies it to destination. If
1332 destination is a file, this method overwrites it. If destination is a
1333 directory, an IOError will be raised.
1335 If self is a link (and followLinks is False), self will be copied
1336 over as a new symlink with the same target as returned by os.readlink.
1337 That means that if it is absolute, both the old and new symlink will
1338 link to the same thing. If it's relative, then perhaps not (and
1339 it's also possible that this relative link will be broken).
1341 File/directory permissions and ownership will NOT be copied over.
1343 If followLinks is True, symlinks are followed so that they're treated
1344 as their targets. In other words, if self is a link, the link's target
1345 will be copied. If destination is a link, self will be copied to the
1346 destination's target (the actual destination will be destination's
1347 target). Symlinks under self (if self is a directory) will be
1348 followed and its target's children be copied recursively.
1350 If followLinks is False, symlinks will be copied over as symlinks.
1352 @param destination: the destination (a FilePath) to which self
1354 @param followLinks: whether symlinks in self should be treated as links
1357 if self.islink() and not followLinks:
1358 os.symlink(os.readlink(self.path), destination.path)
1360 # XXX TODO: *thorough* audit and documentation of the exact desired
1361 # semantics of this code. Right now the behavior of existent
1362 # destination symlinks is convenient, and quite possibly correct, but
1363 # its security properties need to be explained.
1365 if not destination.exists():
1366 destination.createDirectory()
1367 for child in self.children():
1368 destChild = destination.child(child.basename())
1369 child.copyTo(destChild, followLinks)
1371 writefile = destination.open('w')
1373 readfile = self.open()
1376 # XXX TODO: optionally use os.open, os.read and O_DIRECT
1377 # and use os.fstatvfs to determine chunk sizes and make
1378 # *****sure**** copy is page-atomic; the following is
1379 # good enough for 99.9% of everybody and won't take a
1380 # week to audit though.
1381 chunk = readfile.read(self._chunkSize)
1382 writefile.write(chunk)
1383 if len(chunk) < self._chunkSize:
1389 elif not self.exists():
1390 raise OSError(errno.ENOENT, "No such file or directory")
1392 # If you see the following message because you want to copy
1393 # symlinks, fifos, block devices, character devices, or unix
1394 # sockets, please feel free to add support to do sensible things in
1395 # reaction to those types!
1396 raise NotImplementedError(
1397 "Only copying of files and directories supported")
1400 def moveTo(self, destination, followLinks=True):
1402 Move self to destination - basically renaming self to whatever
1403 destination is named. If destination is an already-existing directory,
1404 moves all children to destination if destination is empty. If
1405 destination is a non-empty directory, or destination is a file, an
1406 OSError will be raised.
1408 If moving between filesystems, self needs to be copied, and everything
1409 that applies to copyTo applies to moveTo.
1411 @param destination: the destination (a FilePath) to which self
1413 @param followLinks: whether symlinks in self should be treated as links
1414 or as their targets (only applicable when moving between
1418 os.rename(self.path, destination.path)
1419 except OSError, ose:
1420 if ose.errno == errno.EXDEV:
1421 # man 2 rename, ubuntu linux 5.10 "breezy":
1423 # oldpath and newpath are not on the same mounted filesystem.
1424 # (Linux permits a filesystem to be mounted at multiple
1425 # points, but rename(2) does not work across different mount
1426 # points, even if the same filesystem is mounted on both.)
1428 # that means it's time to copy trees of directories!
1429 secsib = destination.temporarySibling()
1430 self.copyTo(secsib, followLinks) # slow
1431 secsib.moveTo(destination, followLinks) # visible
1433 # done creating new stuff. let's clean me up.
1434 mysecsib = self.temporarySibling()
1435 self.moveTo(mysecsib, followLinks) # visible
1436 mysecsib.remove() # slow
1441 destination.changed()
1444 FilePath.clonePath = FilePath