Initial import to Tizen
[profile/ivi/python-twisted.git] / 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 """
6 Object-oriented filesystem path representation.
7 """
8
9 import os
10 import errno
11 import random
12 import base64
13
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
19
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
24
25
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
29
30 from twisted.python.runtime import platform
31 from twisted.python.hashlib import sha1
32
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
36
37 from twisted.python.util import FancyEqMixin
38
39 from zope.interface import Interface, Attribute, implements
40
41 _CREATE_FLAGS = (os.O_EXCL |
42                  os.O_CREAT |
43                  os.O_RDWR |
44                  O_BINARY)
45
46
47 def _stub_islink(path):
48     """
49     Always return 'false' if the operating system does not support symlinks.
50
51     @param path: a path string.
52     @type path: L{str}
53     @return: false
54     """
55     return False
56
57
58 def _stub_urandom(n):
59     """
60     Provide random data in versions of Python prior to 2.4.  This is an
61     effectively compatible replacement for 'os.urandom'.
62
63     @type n: L{int}
64     @param n: the number of bytes of data to return
65     @return: C{n} bytes of random data.
66     @rtype: str
67     """
68     randomData = [random.randrange(256) for n in xrange(n)]
69     return ''.join(map(chr, randomData))
70
71
72 def _stub_armor(s):
73     """
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
76     available.
77     """
78     return s.encode('hex')
79
80 islink = getattr(os.path, 'islink', _stub_islink)
81 randomBytes = getattr(os, 'urandom', _stub_urandom)
82 armor = getattr(base64, 'urlsafe_b64encode', _stub_armor)
83
84 class IFilePath(Interface):
85     """
86     File path object.
87
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.
91
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".
95
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.
100
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
103     would be C{os.sep}.
104
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
108     meanings.
109
110     @since: 12.1
111     """
112     sep = Attribute("The path separator to use in string representations")
113
114     def child(name):
115         """
116         Obtain a direct child of this file path.  The child may or may not
117         exist.
118
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.
124         """
125
126     def open(mode="r"):
127         """
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.
131         """
132
133     def changed():
134         """
135         Clear any cached information about the state of this path on disk.
136         """
137
138     def getsize():
139         """
140         @return: the size of the file at this file path in bytes.
141         @raise Exception: if the size cannot be obtained.
142         """
143
144     def getModificationTime():
145         """
146         Retrieve the time of last access from this file.
147
148         @return: a number of seconds from the epoch.
149         @rtype: float
150         """
151
152     def getStatusChangeTime():
153         """
154         Retrieve the time of the last status change for this file.
155
156         @return: a number of seconds from the epoch.
157         @rtype: float
158         """
159
160     def getAccessTime():
161         """
162         Retrieve the time that this file was last accessed.
163
164         @return: a number of seconds from the epoch.
165         @rtype: float
166         """
167
168     def exists():
169         """
170         @return: C{True} if the file at this file path exists, C{False}
171             otherwise.
172         """
173
174     def isdir():
175         """
176         @return: C{True} if the file at this file path is a directory, C{False}
177             otherwise.
178         """
179
180     def isfile():
181         """
182         @return: C{True} if the file at this file path is a regular file,
183             C{False} otherwise.
184         """
185
186     def children():
187         """
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.
190         """
191
192     def basename():
193         """
194         @return: the base name of this file path.
195         """
196
197     def parent():
198         """
199         A file path for the directory containing the file at this file path.
200         """
201
202     def sibling(name):
203         """
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.
207
208         @return: a sibling file path of this one.
209         """
210
211 class InsecurePath(Exception):
212     """
213     Error that is raised when the path provided to FilePath is invalid.
214     """
215
216
217
218 class LinkError(Exception):
219     """
220     An error with symlinks - either that there are cyclical symlinks or that
221     symlink are not supported on this platform.
222     """
223
224
225
226 class UnlistableError(OSError):
227     """
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.
230
231     This error will try to look as much like the original error as possible,
232     while still being catchable as an independent type.
233
234     @ivar originalException: the actual original exception instance, either an
235     L{OSError} or a L{WindowsError}.
236     """
237     def __init__(self, originalException):
238         """
239         Create an UnlistableError exception.
240
241         @param originalException: an instance of OSError.
242         """
243         self.__dict__.update(originalException.__dict__)
244         self.originalException = originalException
245
246
247
248 class _WindowsUnlistableError(UnlistableError, WindowsError):
249     """
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().
253
254     It is private because all application code may portably catch
255     L{UnlistableError} instead.
256     """
257
258
259
260 def _secureEnoughString():
261     """
262     Create a pseudorandom, 16-character string for use in secure filenames.
263     """
264     return armor(sha1(randomBytes(64)).digest())[:16]
265
266
267
268 class AbstractFilePath(object):
269     """
270     Abstract implementation of an IFilePath; must be completed by a subclass.
271
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.
275     """
276
277     def getContent(self):
278         fp = self.open()
279         try:
280             return fp.read()
281         finally:
282             fp.close()
283
284
285     def parents(self):
286         """
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.
289         """
290         path = self
291         parent = path.parent()
292         # root.parent() == root, so this means "are we the root"
293         while path != parent:
294             yield parent
295             path = parent
296             parent = parent.parent()
297
298
299     def children(self):
300         """
301         List the children of this path object.
302
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.
307
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.
311
312         @return: an iterable of all currently-existing children of this object
313         accessible with L{_PathHelper.child}.
314         """
315         try:
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
321             # them differently.
322
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
327             # error number.
328
329             # Under Python 2.4 on Windows, WindowsError only has an errno
330             # attribute.  It is bound to the Windows error code.
331
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
334             # version.
335
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,
343                                 ERROR_INVALID_NAME,
344                                 ERROR_DIRECTORY):
345                 raise
346             raise _WindowsUnlistableError(winErrObj)
347         except OSError, ose:
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
352                 raise
353             raise UnlistableError(ose)
354         return map(self.child, subnames)
355
356     def walk(self, descend=None):
357         """
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
364         directories).
365
366         @param descend: A one-argument callable that will return True for
367             FilePaths that should be traversed, False otherwise.
368
369         @return: a generator yielding FilePath-like objects.
370         """
371         yield self
372         if self.isdir():
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.")
381                         yield subc
382                 else:
383                     yield c
384
385
386     def sibling(self, path):
387         """
388         Return a L{FilePath} with the same directory as this instance but with a
389         basename of C{path}.
390
391         @param path: The basename of the L{FilePath} to return.
392         @type path: C{str}
393
394         @rtype: L{FilePath}
395         """
396         return self.parent().child(path)
397
398
399     def descendant(self, segments):
400         """
401         Retrieve a child or child's child of this path.
402
403         @param segments: A sequence of path segments as C{str} instances.
404
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
407             on.
408
409         @since: 10.2
410         """
411         path = self
412         for name in segments:
413             path = path.child(name)
414         return path
415
416
417     def segmentsFrom(self, ancestor):
418         """
419         Return a list of segments between a child and its ancestor.
420
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',
423         'd']}.
424
425         @param ancestor: an instance of the same class as self, ostensibly an
426         ancestor of self.
427
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.
430
431         @return: a list of strs
432         """
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
436         f = self
437         p = f.parent()
438         segments = []
439         while f != ancestor and p != f:
440             segments[0:0] = [f.basename()]
441             f = p
442             p = p.parent()
443         if f == ancestor and segments:
444             return segments
445         raise ValueError("%r not parent of %r" % (ancestor, self))
446
447
448     # new in 8.0
449     def __hash__(self):
450         """
451         Hash the same as another FilePath with the same path as mine.
452         """
453         return hash((self.__class__, self.path))
454
455
456     # pending deprecation in 8.0
457     def getmtime(self):
458         """
459         Deprecated.  Use getModificationTime instead.
460         """
461         return int(self.getModificationTime())
462
463
464     def getatime(self):
465         """
466         Deprecated.  Use getAccessTime instead.
467         """
468         return int(self.getAccessTime())
469
470
471     def getctime(self):
472         """
473         Deprecated.  Use getStatusChangeTime instead.
474         """
475         return int(self.getStatusChangeTime())
476
477
478
479 class RWX(FancyEqMixin, object):
480     """
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?.
484
485     @type read: C{bool}
486     @ivar read: Whether permission to read is given
487
488     @type write: C{bool}
489     @ivar write: Whether permission to write is given
490
491     @type execute: C{bool}
492     @ivar execute: Whether permission to execute is given
493
494     @since: 11.1
495     """
496     compareAttributes = ('read', 'write', 'execute')
497     def __init__(self, readable, writable, executable):
498         self.read = readable
499         self.write = writable
500         self.execute = executable
501
502
503     def __repr__(self):
504         return "RWX(read=%s, write=%s, execute=%s)" % (
505             self.read, self.write, self.execute)
506
507
508     def shorthand(self):
509         """
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'
512         (e.g. 'rwx')
513         """
514         returnval = ['r', 'w', 'x']
515         i = 0
516         for val in (self.read, self.write, self.execute):
517             if not val:
518                 returnval[i] = '-'
519             i += 1
520         return ''.join(returnval)
521
522
523
524 class Permissions(FancyEqMixin, object):
525     """
526     A class representing read/write/execute permissions.  Instantiate with any
527     portion of the file's mode that includes the permission bits.
528
529     @type user: L{RWX}
530     @ivar user: User/Owner permissions
531
532     @type group: L{RWX}
533     @ivar group: Group permissions
534
535     @type other: L{RWX}
536     @ivar other: Other/World permissions
537
538     @since: 11.1
539     """
540
541     compareAttributes = ('user', 'group', 'other')
542
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]]]
549         )
550
551
552     def __repr__(self):
553         return "[%s | %s | %s]" % (
554             str(self.user), str(self.group), str(self.other))
555
556
557     def shorthand(self):
558         """
559         Returns a short string representing the permission bits.  Looks like
560         what is printed by command line utilities such as 'ls -l'
561         (e.g. 'rwx-wx--x')
562         """
563         return "".join(
564             [x.shorthand() for x in (self.user, self.group, self.other)])
565
566
567
568 class FilePath(AbstractFilePath):
569     """
570     I am a path on the filesystem that only permits 'downwards' access.
571
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.
576
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.
581
582     Even if you pass me a relative path, I will convert that to an absolute
583     path internally.
584
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
590     later.
591
592     @type alwaysCreate: C{bool}
593     @ivar alwaysCreate: When opening this file, only succeed if the file does
594         not already exist.
595
596     @type path: C{str}
597     @ivar path: The path from which 'downward' traversal is permitted.
598
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}
612     """
613
614     implements(IFilePath)
615
616     statinfo = None
617     path = None
618
619     sep = slash
620
621     def __init__(self, path, alwaysCreate=False):
622         """
623         Convert a path string to an absolute path if necessary and initialize
624         the L{FilePath} with the result.
625         """
626         self.path = abspath(path)
627         self.alwaysCreate = alwaysCreate
628
629     def __getstate__(self):
630         """
631         Support serialization by discarding cached L{os.stat} results and
632         returning everything else.
633         """
634         d = self.__dict__.copy()
635         if d.has_key('statinfo'):
636             del d['statinfo']
637         return d
638
639
640     def child(self, path):
641         """
642         Create and return a new L{FilePath} representing a path contained by
643         C{self}.
644
645         @param path: The base name of the new L{FilePath}.  If this contains
646             directory separators or parent references it will be rejected.
647         @type path: C{str}
648
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.
651         """
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)
656         if self.sep in norm:
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)
662
663
664     def preauthChild(self, path):
665         """
666         Use me if `path' might have slashes in it, but you know they're safe.
667
668         (NOT slashes at the beginning. It still needs to be a _child_).
669         """
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)
674
675     def childSearchPreauth(self, *paths):
676         """Return my first existing child with a name in 'paths'.
677
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
680         arbitrary user.
681
682         If no appropriately-named children exist, this will return None.
683         """
684         p = self.path
685         for child in paths:
686             jp = joinpath(p, child)
687             if exists(jp):
688                 return self.clonePath(jp)
689
690     def siblingExtensionSearch(self, *exts):
691         """Attempt to return a path with my name, given multiple possible
692         extensions.
693
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
697         be returned.
698
699         The extension '*' has a magic meaning, which means "any path that
700         begins with self.path+'.' is acceptable".
701         """
702         p = self.path
703         for ext in exts:
704             if not ext and self.exists():
705                 return self
706             if ext == '*':
707                 basedot = basename(p)+'.'
708                 for fn in listdir(dirname(p)):
709                     if fn.startswith(basedot):
710                         return self.clonePath(joinpath(dirname(p), fn))
711             p2 = p + ext
712             if exists(p2):
713                 return self.clonePath(p2)
714
715
716     def realpath(self):
717         """
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.
723
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).
727
728         @return: FilePath of the target path
729         @raises LinkError: if links are not supported or links are cyclical.
730         """
731         if self.islink():
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)
736         return self
737
738
739     def siblingExtension(self, ext):
740         return self.clonePath(self.path+ext)
741
742
743     def linkTo(self, linkFilePath):
744         """
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
749         exists.
750
751         @param linkFilePath: a FilePath representing the link to be created
752         @type linkFilePath: L{FilePath}
753         """
754         os.symlink(self.path, linkFilePath.path)
755
756
757     def open(self, mode='r'):
758         """
759         Open this file using C{mode} or for writing if C{alwaysCreate} is
760         C{True}.
761
762         In all cases the file is opened in binary mode, so it is not necessary
763         to include C{b} in C{mode}.
764
765         @param mode: The mode to open the file in.  Default is C{r}.
766         @type mode: C{str}
767         @raises AssertionError: If C{a} is included in the mode and
768             C{alwaysCreate} is C{True}.
769         @rtype: C{file}
770         @return: An open C{file} object.
771         """
772         if self.alwaysCreate:
773             assert 'a' not in mode, ("Appending not supported when "
774                                      "alwaysCreate == True")
775             return self.create()
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')
780
781     # stat methods below
782
783     def restat(self, reraise=True):
784         """
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.
787
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.
791
792         @raise Exception: is C{reraise} is C{True} and an exception occurs while
793             reloading metadata.
794         """
795         try:
796             self.statinfo = stat(self.path)
797         except OSError:
798             self.statinfo = 0
799             if reraise:
800                 raise
801
802
803     def changed(self):
804         """
805         Clear any cached information about the state of this path on disk.
806
807         @since: 10.1.0
808         """
809         self.statinfo = None
810
811
812     def chmod(self, mode):
813         """
814         Changes the permissions on self, if possible.  Propagates errors from
815         C{os.chmod} up.
816
817         @param mode: integer representing the new permissions desired (same as
818             the command line chmod)
819         @type mode: C{int}
820         """
821         os.chmod(self.path, mode)
822
823
824     def getsize(self):
825         st = self.statinfo
826         if not st:
827             self.restat()
828             st = self.statinfo
829         return st.st_size
830
831
832     def getModificationTime(self):
833         """
834         Retrieve the time of last access from this file.
835
836         @return: a number of seconds from the epoch.
837         @rtype: float
838         """
839         st = self.statinfo
840         if not st:
841             self.restat()
842             st = self.statinfo
843         return float(st.st_mtime)
844
845
846     def getStatusChangeTime(self):
847         """
848         Retrieve the time of the last status change for this file.
849
850         @return: a number of seconds from the epoch.
851         @rtype: float
852         """
853         st = self.statinfo
854         if not st:
855             self.restat()
856             st = self.statinfo
857         return float(st.st_ctime)
858
859
860     def getAccessTime(self):
861         """
862         Retrieve the time that this file was last accessed.
863
864         @return: a number of seconds from the epoch.
865         @rtype: float
866         """
867         st = self.statinfo
868         if not st:
869             self.restat()
870             st = self.statinfo
871         return float(st.st_atime)
872
873
874     def getInodeNumber(self):
875         """
876         Retrieve the file serial number, also called inode number, which 
877         distinguishes this file from all other files on the same device.
878
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
882         @rtype: C{long}
883         @since: 11.0
884         """
885         if platform.isWindows():
886             raise NotImplementedError
887
888         st = self.statinfo
889         if not st:
890             self.restat()
891             st = self.statinfo
892         return long(st.st_ino)
893
894
895     def getDevice(self):
896         """
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.
900
901         @raise: NotImplementedError if the platform is Windows, since the
902                 device number would be 0 for all partitions on a Windows
903                 platform
904         @return: a number representing the device
905         @rtype: C{long}
906         @since: 11.0
907         """
908         if platform.isWindows():
909             raise NotImplementedError
910
911         st = self.statinfo
912         if not st:
913             self.restat()
914             st = self.statinfo
915         return long(st.st_dev)
916
917
918     def getNumberOfHardLinks(self):
919         """
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.
925
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
930         @rtype: C{int}
931         @since: 11.0
932         """
933         if platform.isWindows():
934             raise NotImplementedError
935
936         st = self.statinfo
937         if not st:
938             self.restat()
939             st = self.statinfo
940         return int(st.st_nlink)
941
942
943     def getUserID(self):
944         """
945         Returns the user ID of the file's owner.
946
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
950         @rtype: C{int}
951         @since: 11.0
952         """
953         if platform.isWindows():
954             raise NotImplementedError
955
956         st = self.statinfo
957         if not st:
958             self.restat()
959             st = self.statinfo
960         return int(st.st_uid)
961
962
963     def getGroupID(self):
964         """
965         Returns the group ID of the file.
966
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
970         @rtype: C{int}
971         @since: 11.0
972         """
973         if platform.isWindows():
974             raise NotImplementedError
975
976         st = self.statinfo
977         if not st:
978             self.restat()
979             st = self.statinfo
980         return int(st.st_gid)
981
982
983     def getPermissions(self):
984         """
985         Returns the permissions of the file.  Should also work on Windows,
986         however, those permissions may not what is expected in Windows.
987
988         @return: the permissions for the file
989         @rtype: L{Permissions}
990         @since: 11.1
991         """
992         st = self.statinfo
993         if not st:
994             self.restat()
995             st = self.statinfo
996         return Permissions(S_IMODE(st.st_mode))
997
998
999     def exists(self):
1000         """
1001         Check if this L{FilePath} exists.
1002
1003         @return: C{True} if the stats of C{path} can be retrieved successfully,
1004             C{False} in the other cases.
1005         @rtype: C{bool}
1006         """
1007         if self.statinfo:
1008             return True
1009         else:
1010             self.restat(False)
1011             if self.statinfo:
1012                 return True
1013             else:
1014                 return False
1015
1016
1017     def isdir(self):
1018         """
1019         @return: C{True} if this L{FilePath} refers to a directory, C{False}
1020             otherwise.
1021         """
1022         st = self.statinfo
1023         if not st:
1024             self.restat(False)
1025             st = self.statinfo
1026             if not st:
1027                 return False
1028         return S_ISDIR(st.st_mode)
1029
1030
1031     def isfile(self):
1032         """
1033         @return: C{True} if this L{FilePath} points to a regular file (not a
1034             directory, socket, named pipe, etc), C{False} otherwise.
1035         """
1036         st = self.statinfo
1037         if not st:
1038             self.restat(False)
1039             st = self.statinfo
1040             if not st:
1041                 return False
1042         return S_ISREG(st.st_mode)
1043
1044
1045     def isBlockDevice(self):
1046         """
1047         Returns whether the underlying path is a block device.
1048
1049         @return: C{True} if it is a block device, C{False} otherwise 
1050         @rtype: C{bool}
1051         @since: 11.1
1052         """
1053         st = self.statinfo
1054         if not st:
1055             self.restat(False)
1056             st = self.statinfo
1057             if not st:
1058                 return False
1059         return S_ISBLK(st.st_mode)
1060
1061
1062     def isSocket(self):
1063         """
1064         Returns whether the underlying path is a socket.
1065
1066         @return: C{True} if it is a socket, C{False} otherwise 
1067         @rtype: C{bool}
1068         @since: 11.1
1069         """
1070         st = self.statinfo
1071         if not st:
1072             self.restat(False)
1073             st = self.statinfo
1074             if not st:
1075                 return False
1076         return S_ISSOCK(st.st_mode)
1077
1078
1079     def islink(self):
1080         """
1081         @return: C{True} if this L{FilePath} points to a symbolic link.
1082         """
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)
1088
1089
1090     def isabs(self):
1091         """
1092         @return: C{True}, always.
1093         """
1094         return isabs(self.path)
1095
1096
1097     def listdir(self):
1098         """
1099         List the base names of the direct children of this L{FilePath}.
1100
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
1103             this L{FilePath}.
1104
1105         @raise: Anything the platform C{os.listdir} implementation might raise
1106             (typically OSError).
1107         """
1108         return listdir(self.path)
1109
1110
1111     def splitext(self):
1112         """
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}
1115         """
1116         return splitext(self.path)
1117
1118
1119     def __repr__(self):
1120         return 'FilePath(%r)' % (self.path,)
1121
1122
1123     def touch(self):
1124         """
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
1127         already exist.
1128
1129         @raise Exception: if unable to create or modify the last modification
1130             time of the file.
1131         """
1132         try:
1133             self.open('a').close()
1134         except IOError:
1135             pass
1136         utime(self.path, None)
1137
1138
1139     def remove(self):
1140         """
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.
1144         """
1145         if self.isdir() and not self.islink():
1146             for child in self.children():
1147                 child.remove()
1148             os.rmdir(self.path)
1149         else:
1150             os.remove(self.path)
1151         self.changed()
1152
1153
1154     def makedirs(self):
1155         """
1156         Create all directories not yet existing in C{path} segments, using
1157         C{os.makedirs}.
1158         """
1159         return os.makedirs(self.path)
1160
1161
1162     def globChildren(self, pattern):
1163         """
1164         Assuming I am representing a directory, return a list of
1165         FilePaths representing my children that match the given
1166         pattern.
1167         """
1168         import glob
1169         path = self.path[-1] == '/' and self.path + pattern or self.sep.join([self.path, pattern])
1170         return map(self.clonePath, glob.glob(path))
1171
1172
1173     def basename(self):
1174         """
1175         @return: The final component of the L{FilePath}'s path (Everything after
1176             the final path separator).
1177         @rtype: C{str}
1178         """
1179         return basename(self.path)
1180
1181
1182     def dirname(self):
1183         """
1184         @return: All of the components of the L{FilePath}'s path except the last
1185             one (everything up to the final path separator).
1186         @rtype: C{str}
1187         """
1188         return dirname(self.path)
1189
1190
1191     def parent(self):
1192         """
1193         @return: A L{FilePath} representing the path which directly contains
1194             this L{FilePath}.
1195         """
1196         return self.clonePath(self.dirname())
1197
1198
1199     def setContent(self, content, ext='.new'):
1200         """
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.
1203
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:
1209
1210             - your filesystem is journaled (i.e. your filesystem will not
1211               I{itself} lose data due to power loss)
1212
1213             - your filesystem's C{rename()} is atomic
1214
1215             - your filesystem will not discard new data while preserving new
1216               metadata (see U{http://mjg59.livejournal.com/108257.html} for more
1217               detail)
1218
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>}.
1229
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
1233         same time.
1234
1235         @param content: The desired contents of the file at this path.
1236
1237         @type content: L{str}
1238
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.
1243
1244         @type ext: C{str}
1245         """
1246         sib = self.temporarySibling(ext)
1247         f = sib.open('w')
1248         try:
1249             f.write(content)
1250         finally:
1251             f.close()
1252         if platform.isWindows() and exists(self.path):
1253             os.unlink(self.path)
1254         os.rename(sib.path, self.path)
1255
1256
1257     # new in 2.2.0
1258
1259     def __cmp__(self, other):
1260         if not isinstance(other, FilePath):
1261             return NotImplemented
1262         return cmp(self.path, other.path)
1263
1264
1265     def createDirectory(self):
1266         """
1267         Create the directory the L{FilePath} refers to.
1268
1269         @see: L{makedirs}
1270
1271         @raise OSError: If the directory cannot be created.
1272         """
1273         os.mkdir(self.path)
1274
1275
1276     def requireCreate(self, val=1):
1277         self.alwaysCreate = val
1278
1279
1280     def create(self):
1281         """
1282         Exclusively create a file, only if this file previously did not exist.
1283         """
1284         fdint = os.open(self.path, _CREATE_FLAGS)
1285
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...
1289
1290         return os.fdopen(fdint, 'w+b')
1291
1292
1293     def temporarySibling(self, extension=""):
1294         """
1295         Construct a path referring to a sibling of this path.
1296
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
1300         advance.
1301
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 '.'
1304             yourself.)
1305
1306         @type extension: C{str}
1307
1308         @return: a path object with the given extension suffix, C{alwaysCreate}
1309             set to True.
1310
1311         @rtype: L{FilePath}
1312         """
1313         sib = self.sibling(_secureEnoughString() + self.basename() + extension)
1314         sib.requireCreate()
1315         return sib
1316
1317
1318     _chunkSize = 2 ** 2 ** 2 ** 2
1319
1320     def copyTo(self, destination, followLinks=True):
1321         """
1322         Copies self to destination.
1323
1324         If self doesn't exist, an OSError is raised.
1325
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.
1330
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.
1334
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).
1340
1341         File/directory permissions and ownership will NOT be copied over.
1342
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.
1349
1350         If followLinks is False, symlinks will be copied over as symlinks.
1351
1352         @param destination: the destination (a FilePath) to which self
1353             should be copied
1354         @param followLinks: whether symlinks in self should be treated as links
1355             or as their targets
1356         """
1357         if self.islink() and not followLinks:
1358             os.symlink(os.readlink(self.path), destination.path)
1359             return
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.
1364         if self.isdir():
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)
1370         elif self.isfile():
1371             writefile = destination.open('w')
1372             try:
1373                 readfile = self.open()
1374                 try:
1375                     while 1:
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:
1384                             break
1385                 finally:
1386                     readfile.close()
1387             finally:
1388                 writefile.close()
1389         elif not self.exists():
1390             raise OSError(errno.ENOENT, "No such file or directory")
1391         else:
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")
1398
1399
1400     def moveTo(self, destination, followLinks=True):
1401         """
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.
1407
1408         If moving between filesystems, self needs to be copied, and everything
1409         that applies to copyTo applies to moveTo.
1410
1411         @param destination: the destination (a FilePath) to which self
1412             should be copied
1413         @param followLinks: whether symlinks in self should be treated as links
1414             or as their targets (only applicable when moving between
1415             filesystems)
1416         """
1417         try:
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":
1422
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.)
1427
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
1432
1433                 # done creating new stuff.  let's clean me up.
1434                 mysecsib = self.temporarySibling()
1435                 self.moveTo(mysecsib, followLinks) # visible
1436                 mysecsib.remove() # slow
1437             else:
1438                 raise
1439         else:
1440             self.changed()
1441             destination.changed()
1442
1443
1444 FilePath.clonePath = FilePath