~0x44/nova/extdoc

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/python/filepath.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_paths -*-
 
2
# Copyright (c) 2001-2008 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
 
21
 
 
22
# Please keep this as light as possible on other Twisted imports; many, many
 
23
# things import this module, and it would be good if it could easily be
 
24
# modified for inclusion in the standard library.  --glyph
 
25
 
 
26
from twisted.python.runtime import platform
 
27
from twisted.python.hashlib import sha1
 
28
 
 
29
from twisted.python.win32 import ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND
 
30
from twisted.python.win32 import ERROR_INVALID_NAME, ERROR_DIRECTORY
 
31
from twisted.python.win32 import WindowsError
 
32
 
 
33
def _stub_islink(path):
 
34
    """
 
35
    Always return 'false' if the operating system does not support symlinks.
 
36
 
 
37
    @param path: a path string.
 
38
    @type path: L{str}
 
39
    @return: false
 
40
    """
 
41
    return False
 
42
 
 
43
 
 
44
def _stub_urandom(n):
 
45
    """
 
46
    Provide random data in versions of Python prior to 2.4.  This is an
 
47
    effectively compatible replacement for 'os.urandom'.
 
48
 
 
49
    @type n: L{int}
 
50
    @param n: the number of bytes of data to return
 
51
    @return: C{n} bytes of random data.
 
52
    @rtype: str
 
53
    """
 
54
    randomData = [random.randrange(256) for n in xrange(n)]
 
55
    return ''.join(map(chr, randomData))
 
56
 
 
57
 
 
58
def _stub_armor(s):
 
59
    """
 
60
    ASCII-armor for random data.  This uses a hex encoding, although we will
 
61
    prefer url-safe base64 encoding for features in this module if it is
 
62
    available.
 
63
    """
 
64
    return s.encode('hex')
 
65
 
 
66
islink = getattr(os.path, 'islink', _stub_islink)
 
67
randomBytes = getattr(os, 'urandom', _stub_urandom)
 
68
armor = getattr(base64, 'urlsafe_b64encode', _stub_armor)
 
69
 
 
70
class InsecurePath(Exception):
 
71
    """
 
72
    Error that is raised when the path provided to FilePath is invalid.
 
73
    """
 
74
 
 
75
 
 
76
 
 
77
class LinkError(Exception):
 
78
    """
 
79
    An error with symlinks - either that there are cyclical symlinks or that
 
80
    symlink are not supported on this platform.
 
81
    """
 
82
 
 
83
 
 
84
 
 
85
class UnlistableError(OSError):
 
86
    """
 
87
    An exception which is used to distinguish between errors which mean 'this
 
88
    is not a directory you can list' and other, more catastrophic errors.
 
89
 
 
90
    This error will try to look as much like the original error as possible,
 
91
    while still being catchable as an independent type.
 
92
 
 
93
    @ivar originalException: the actual original exception instance, either an
 
94
    L{OSError} or a L{WindowsError}.
 
95
    """
 
96
    def __init__(self, originalException):
 
97
        """
 
98
        Create an UnlistableError exception.
 
99
 
 
100
        @param originalException: an instance of OSError.
 
101
        """
 
102
        self.__dict__.update(originalException.__dict__)
 
103
        self.originalException = originalException
 
104
 
 
105
 
 
106
 
 
107
class _WindowsUnlistableError(UnlistableError, WindowsError):
 
108
    """
 
109
    This exception is raised on Windows, for compatibility with previous
 
110
    releases of FilePath where unportable programs may have done "except
 
111
    WindowsError:" around a call to children().
 
112
 
 
113
    It is private because all application code may portably catch
 
114
    L{UnlistableError} instead.
 
115
    """
 
116
 
 
117
 
 
118
 
 
119
def _secureEnoughString():
 
120
    """
 
121
    Create a pseudorandom, 16-character string for use in secure filenames.
 
122
    """
 
123
    return armor(sha1(randomBytes(64)).digest())[:16]
 
124
 
 
125
 
 
126
 
 
127
class _PathHelper:
 
128
    """
 
129
    Abstract helper class also used by ZipPath; implements certain utility
 
130
    methods.
 
131
    """
 
132
 
 
133
    def getContent(self):
 
134
        return self.open().read()
 
135
 
 
136
 
 
137
    def parents(self):
 
138
        """
 
139
        @return: an iterator of all the ancestors of this path, from the most
 
140
        recent (its immediate parent) to the root of its filesystem.
 
141
        """
 
142
        path = self
 
143
        parent = path.parent()
 
144
        # root.parent() == root, so this means "are we the root"
 
145
        while path != parent:
 
146
            yield parent
 
147
            path = parent
 
148
            parent = parent.parent()
 
149
 
 
150
 
 
151
    def children(self):
 
152
        """
 
153
        List the chilren of this path object.
 
154
 
 
155
        @raise OSError: If an error occurs while listing the directory.  If the
 
156
        error is 'serious', meaning that the operation failed due to an access
 
157
        violation, exhaustion of some kind of resource (file descriptors or
 
158
        memory), OSError or a platform-specific variant will be raised.
 
159
 
 
160
        @raise UnlistableError: If the inability to list the directory is due
 
161
        to this path not existing or not being a directory, the more specific
 
162
        OSError subclass L{UnlistableError} is raised instead.
 
163
 
 
164
        @return: an iterable of all currently-existing children of this object
 
165
        accessible with L{_PathHelper.child}.
 
166
        """
 
167
        try:
 
168
            subnames = self.listdir()
 
169
        except WindowsError, winErrObj:
 
170
            # WindowsError is an OSError subclass, so if not for this clause
 
171
            # the OSError clause below would be handling these.  Windows error
 
172
            # codes aren't the same as POSIX error codes, so we need to handle
 
173
            # them differently.
 
174
 
 
175
            # Under Python 2.5 on Windows, WindowsError has a winerror
 
176
            # attribute and an errno attribute.  The winerror attribute is
 
177
            # bound to the Windows error code while the errno attribute is
 
178
            # bound to a translation of that code to a perhaps equivalent POSIX
 
179
            # error number.
 
180
 
 
181
            # Under Python 2.4 on Windows, WindowsError only has an errno
 
182
            # attribute.  It is bound to the Windows error code.
 
183
 
 
184
            # For simplicity of code and to keep the number of paths through
 
185
            # this suite minimal, we grab the Windows error code under either
 
186
            # version.
 
187
 
 
188
            # Furthermore, attempting to use os.listdir on a non-existent path
 
189
            # in Python 2.4 will result in a Windows error code of
 
190
            # ERROR_PATH_NOT_FOUND.  However, in Python 2.5,
 
191
            # ERROR_FILE_NOT_FOUND results instead. -exarkun
 
192
            winerror = getattr(winErrObj, 'winerror', winErrObj.errno)
 
193
            if winerror not in (ERROR_PATH_NOT_FOUND,
 
194
                                ERROR_FILE_NOT_FOUND,
 
195
                                ERROR_INVALID_NAME,
 
196
                                ERROR_DIRECTORY):
 
197
                raise
 
198
            raise _WindowsUnlistableError(winErrObj)
 
199
        except OSError, ose:
 
200
            if ose.errno not in (errno.ENOENT, errno.ENOTDIR):
 
201
                # Other possible errors here, according to linux manpages:
 
202
                # EACCES, EMIFLE, ENFILE, ENOMEM.  None of these seem like the
 
203
                # sort of thing which should be handled normally. -glyph
 
204
                raise
 
205
            raise UnlistableError(ose)
 
206
        return map(self.child, subnames)
 
207
 
 
208
    def walk(self, descend=None):
 
209
        """
 
210
        Yield myself, then each of my children, and each of those children's
 
211
        children in turn.  The optional argument C{descend} is a predicate that
 
212
        takes a FilePath, and determines whether or not that FilePath is
 
213
        traversed/descended into.  It will be called with each path for which
 
214
        C{isdir} returns C{True}.  If C{descend} is not specified, all
 
215
        directories will be traversed (including symbolic links which refer to
 
216
        directories).
 
217
 
 
218
        @param descend: A one-argument callable that will return True for
 
219
            FilePaths that should be traversed, False otherwise.
 
220
 
 
221
        @return: a generator yielding FilePath-like objects.
 
222
        """
 
223
        yield self
 
224
        if self.isdir():
 
225
            for c in self.children():
 
226
                # we should first see if it's what we want, then we
 
227
                # can walk through the directory
 
228
                if (descend is None or descend(c)):
 
229
                    for subc in c.walk(descend):
 
230
                        if os.path.realpath(self.path).startswith(
 
231
                            os.path.realpath(subc.path)):
 
232
                            raise LinkError("Cycle in file graph.")
 
233
                        yield subc
 
234
                else:
 
235
                    yield c
 
236
 
 
237
 
 
238
    def sibling(self, path):
 
239
        return self.parent().child(path)
 
240
 
 
241
    def segmentsFrom(self, ancestor):
 
242
        """
 
243
        Return a list of segments between a child and its ancestor.
 
244
 
 
245
        For example, in the case of a path X representing /a/b/c/d and a path Y
 
246
        representing /a/b, C{Y.segmentsFrom(X)} will return C{['c',
 
247
        'd']}.
 
248
 
 
249
        @param ancestor: an instance of the same class as self, ostensibly an
 
250
        ancestor of self.
 
251
 
 
252
        @raise: ValueError if the 'ancestor' parameter is not actually an
 
253
        ancestor, i.e. a path for /x/y/z is passed as an ancestor for /a/b/c/d.
 
254
 
 
255
        @return: a list of strs
 
256
        """
 
257
        # this might be an unnecessarily inefficient implementation but it will
 
258
        # work on win32 and for zipfiles; later I will deterimine if the
 
259
        # obvious fast implemenation does the right thing too
 
260
        f = self
 
261
        p = f.parent()
 
262
        segments = []
 
263
        while f != ancestor and p != f:
 
264
            segments[0:0] = [f.basename()]
 
265
            f = p
 
266
            p = p.parent()
 
267
        if f == ancestor and segments:
 
268
            return segments
 
269
        raise ValueError("%r not parent of %r" % (ancestor, self))
 
270
 
 
271
 
 
272
    # new in 8.0
 
273
    def __hash__(self):
 
274
        """
 
275
        Hash the same as another FilePath with the same path as mine.
 
276
        """
 
277
        return hash((self.__class__, self.path))
 
278
 
 
279
 
 
280
    # pending deprecation in 8.0
 
281
    def getmtime(self):
 
282
        """
 
283
        Deprecated.  Use getModificationTime instead.
 
284
        """
 
285
        return int(self.getModificationTime())
 
286
 
 
287
 
 
288
    def getatime(self):
 
289
        """
 
290
        Deprecated.  Use getAccessTime instead.
 
291
        """
 
292
        return int(self.getAccessTime())
 
293
 
 
294
 
 
295
    def getctime(self):
 
296
        """
 
297
        Deprecated.  Use getStatusChangeTime instead.
 
298
        """
 
299
        return int(self.getStatusChangeTime())
 
300
 
 
301
 
 
302
 
 
303
class FilePath(_PathHelper):
 
304
    """
 
305
    I am a path on the filesystem that only permits 'downwards' access.
 
306
 
 
307
    Instantiate me with a pathname (for example,
 
308
    FilePath('/home/myuser/public_html')) and I will attempt to only provide
 
309
    access to files which reside inside that path.  I may be a path to a file,
 
310
    a directory, or a file which does not exist.
 
311
 
 
312
    The correct way to use me is to instantiate me, and then do ALL filesystem
 
313
    access through me.  In other words, do not import the 'os' module; if you
 
314
    need to open a file, call my 'open' method.  If you need to list a
 
315
    directory, call my 'path' method.
 
316
 
 
317
    Even if you pass me a relative path, I will convert that to an absolute
 
318
    path internally.
 
319
 
 
320
    Note: although time-related methods do return floating-point results, they
 
321
    may still be only second resolution depending on the platform and the last
 
322
    value passed to L{os.stat_float_times}.  If you want greater-than-second
 
323
    precision, call C{os.stat_float_times(True)}, or use Python 2.5.
 
324
    Greater-than-second precision is only available in Windows on Python2.5 and
 
325
    later.
 
326
 
 
327
    @type alwaysCreate: C{bool}
 
328
    @ivar alwaysCreate: When opening this file, only succeed if the file does not
 
329
    already exist.
 
330
    """
 
331
 
 
332
    statinfo = None
 
333
    path = None
 
334
 
 
335
    def __init__(self, path, alwaysCreate=False):
 
336
        self.path = abspath(path)
 
337
        self.alwaysCreate = alwaysCreate
 
338
 
 
339
    def __getstate__(self):
 
340
        d = self.__dict__.copy()
 
341
        if d.has_key('statinfo'):
 
342
            del d['statinfo']
 
343
        return d
 
344
 
 
345
    def child(self, path):
 
346
        if platform.isWindows() and path.count(":"):
 
347
            # Catch paths like C:blah that don't have a slash
 
348
            raise InsecurePath("%r contains a colon." % (path,))
 
349
        norm = normpath(path)
 
350
        if slash in norm:
 
351
            raise InsecurePath("%r contains one or more directory separators" % (path,))
 
352
        newpath = abspath(joinpath(self.path, norm))
 
353
        if not newpath.startswith(self.path):
 
354
            raise InsecurePath("%r is not a child of %s" % (newpath, self.path))
 
355
        return self.clonePath(newpath)
 
356
 
 
357
    def preauthChild(self, path):
 
358
        """
 
359
        Use me if `path' might have slashes in it, but you know they're safe.
 
360
 
 
361
        (NOT slashes at the beginning. It still needs to be a _child_).
 
362
        """
 
363
        newpath = abspath(joinpath(self.path, normpath(path)))
 
364
        if not newpath.startswith(self.path):
 
365
            raise InsecurePath("%s is not a child of %s" % (newpath, self.path))
 
366
        return self.clonePath(newpath)
 
367
 
 
368
    def childSearchPreauth(self, *paths):
 
369
        """Return my first existing child with a name in 'paths'.
 
370
 
 
371
        paths is expected to be a list of *pre-secured* path fragments; in most
 
372
        cases this will be specified by a system administrator and not an
 
373
        arbitrary user.
 
374
 
 
375
        If no appropriately-named children exist, this will return None.
 
376
        """
 
377
        p = self.path
 
378
        for child in paths:
 
379
            jp = joinpath(p, child)
 
380
            if exists(jp):
 
381
                return self.clonePath(jp)
 
382
 
 
383
    def siblingExtensionSearch(self, *exts):
 
384
        """Attempt to return a path with my name, given multiple possible
 
385
        extensions.
 
386
 
 
387
        Each extension in exts will be tested and the first path which exists
 
388
        will be returned.  If no path exists, None will be returned.  If '' is
 
389
        in exts, then if the file referred to by this path exists, 'self' will
 
390
        be returned.
 
391
 
 
392
        The extension '*' has a magic meaning, which means "any path that
 
393
        begins with self.path+'.' is acceptable".
 
394
        """
 
395
        p = self.path
 
396
        for ext in exts:
 
397
            if not ext and self.exists():
 
398
                return self
 
399
            if ext == '*':
 
400
                basedot = basename(p)+'.'
 
401
                for fn in listdir(dirname(p)):
 
402
                    if fn.startswith(basedot):
 
403
                        return self.clonePath(joinpath(dirname(p), fn))
 
404
            p2 = p + ext
 
405
            if exists(p2):
 
406
                return self.clonePath(p2)
 
407
 
 
408
 
 
409
    def realpath(self):
 
410
        """
 
411
        Returns the absolute target as a FilePath if self is a link, self
 
412
        otherwise.  The absolute link is the ultimate file or directory the
 
413
        link refers to (for instance, if the link refers to another link, and
 
414
        another...).  If the filesystem does not support symlinks, or
 
415
        if the link is cyclical, raises a LinkError.
 
416
 
 
417
        Behaves like L{os.path.realpath} in that it does not resolve link
 
418
        names in the middle (ex. /x/y/z, y is a link to w - realpath on z
 
419
        will return /x/y/z, not /x/w/z).
 
420
 
 
421
        @return: FilePath of the target path
 
422
        @raises LinkError: if links are not supported or links are cyclical.
 
423
        """
 
424
        if self.islink():
 
425
            result = os.path.realpath(self.path)
 
426
            if result == self.path:
 
427
                raise LinkError("Cyclical link - will loop forever")
 
428
            return self.clonePath(result)
 
429
        return self
 
430
 
 
431
 
 
432
    def siblingExtension(self, ext):
 
433
        return self.clonePath(self.path+ext)
 
434
 
 
435
 
 
436
    def linkTo(self, linkFilePath):
 
437
        """
 
438
        Creates a symlink to self to at the path in the L{FilePath}
 
439
        C{linkFilePath}.  Only works on posix systems due to its dependence on
 
440
        C{os.symlink}.  Propagates C{OSError}s up from C{os.symlink} if
 
441
        C{linkFilePath.parent()} does not exist, or C{linkFilePath} already
 
442
        exists.
 
443
 
 
444
        @param linkFilePath: a FilePath representing the link to be created
 
445
        @type linkFilePath: L{FilePath}
 
446
        """
 
447
        os.symlink(self.path, linkFilePath.path)
 
448
 
 
449
 
 
450
    def open(self, mode='r'):
 
451
        if self.alwaysCreate:
 
452
            assert 'a' not in mode, "Appending not supported when alwaysCreate == True"
 
453
            return self.create()
 
454
        return open(self.path, mode+'b')
 
455
 
 
456
    # stat methods below
 
457
 
 
458
    def restat(self, reraise=True):
 
459
        """
 
460
        Re-calculate cached effects of 'stat'.  To refresh information on this path
 
461
        after you know the filesystem may have changed, call this method.
 
462
 
 
463
        @param reraise: a boolean.  If true, re-raise exceptions from
 
464
        L{os.stat}; otherwise, mark this path as not existing, and remove any
 
465
        cached stat information.
 
466
        """
 
467
        try:
 
468
            self.statinfo = stat(self.path)
 
469
        except OSError:
 
470
            self.statinfo = 0
 
471
            if reraise:
 
472
                raise
 
473
 
 
474
 
 
475
    def chmod(self, mode):
 
476
        """
 
477
        Changes the permissions on self, if possible.  Propagates errors from
 
478
        C{os.chmod} up.
 
479
 
 
480
        @param mode: integer representing the new permissions desired (same as
 
481
            the command line chmod)
 
482
        @type mode: C{int}
 
483
        """
 
484
        os.chmod(self.path, mode)
 
485
 
 
486
 
 
487
    def getsize(self):
 
488
        st = self.statinfo
 
489
        if not st:
 
490
            self.restat()
 
491
            st = self.statinfo
 
492
        return st.st_size
 
493
 
 
494
 
 
495
    def getModificationTime(self):
 
496
        """
 
497
        Retrieve the time of last access from this file.
 
498
 
 
499
        @return: a number of seconds from the epoch.
 
500
        @rtype: float
 
501
        """
 
502
        st = self.statinfo
 
503
        if not st:
 
504
            self.restat()
 
505
            st = self.statinfo
 
506
        return float(st.st_mtime)
 
507
 
 
508
 
 
509
    def getStatusChangeTime(self):
 
510
        """
 
511
        Retrieve the time of the last status change for this file.
 
512
 
 
513
        @return: a number of seconds from the epoch.
 
514
        @rtype: float
 
515
        """
 
516
        st = self.statinfo
 
517
        if not st:
 
518
            self.restat()
 
519
            st = self.statinfo
 
520
        return float(st.st_ctime)
 
521
 
 
522
 
 
523
    def getAccessTime(self):
 
524
        """
 
525
        Retrieve the time that this file was last accessed.
 
526
 
 
527
        @return: a number of seconds from the epoch.
 
528
        @rtype: float
 
529
        """
 
530
        st = self.statinfo
 
531
        if not st:
 
532
            self.restat()
 
533
            st = self.statinfo
 
534
        return float(st.st_atime)
 
535
 
 
536
 
 
537
    def exists(self):
 
538
        """
 
539
        Check if the C{path} exists.
 
540
 
 
541
        @return: C{True} if the stats of C{path} can be retrieved successfully,
 
542
            C{False} in the other cases.
 
543
        @rtype: C{bool}
 
544
        """
 
545
        if self.statinfo:
 
546
            return True
 
547
        else:
 
548
            self.restat(False)
 
549
            if self.statinfo:
 
550
                return True
 
551
            else:
 
552
                return False
 
553
 
 
554
 
 
555
    def isdir(self):
 
556
        st = self.statinfo
 
557
        if not st:
 
558
            self.restat(False)
 
559
            st = self.statinfo
 
560
            if not st:
 
561
                return False
 
562
        return S_ISDIR(st.st_mode)
 
563
 
 
564
    def isfile(self):
 
565
        st = self.statinfo
 
566
        if not st:
 
567
            self.restat(False)
 
568
            st = self.statinfo
 
569
            if not st:
 
570
                return False
 
571
        return S_ISREG(st.st_mode)
 
572
 
 
573
    def islink(self):
 
574
        # We can't use cached stat results here, because that is the stat of
 
575
        # the destination - (see #1773) which in *every case* but this one is
 
576
        # the right thing to use.  We could call lstat here and use that, but
 
577
        # it seems unlikely we'd actually save any work that way.  -glyph
 
578
        return islink(self.path)
 
579
 
 
580
    def isabs(self):
 
581
        return isabs(self.path)
 
582
 
 
583
    def listdir(self):
 
584
        return listdir(self.path)
 
585
 
 
586
    def splitext(self):
 
587
        return splitext(self.path)
 
588
 
 
589
    def __repr__(self):
 
590
        return 'FilePath(%r)' % (self.path,)
 
591
 
 
592
    def touch(self):
 
593
        try:
 
594
            self.open('a').close()
 
595
        except IOError:
 
596
            pass
 
597
        utime(self.path, None)
 
598
 
 
599
    def remove(self):
 
600
        """
 
601
        Removes the file or directory that is represented by self.  If
 
602
        C{self.path} is a directory, recursively remove all its children
 
603
        before removing the directory.  If it's a file or link, just delete
 
604
        it.
 
605
        """
 
606
        if self.isdir() and not self.islink():
 
607
            for child in self.children():
 
608
                child.remove()
 
609
            os.rmdir(self.path)
 
610
        else:
 
611
            os.remove(self.path)
 
612
        self.restat(False)
 
613
 
 
614
 
 
615
    def makedirs(self):
 
616
        """
 
617
        Create all directories not yet existing in C{path} segments, using
 
618
        C{os.makedirs}.
 
619
        """
 
620
        return os.makedirs(self.path)
 
621
 
 
622
 
 
623
    def globChildren(self, pattern):
 
624
        """
 
625
        Assuming I am representing a directory, return a list of
 
626
        FilePaths representing my children that match the given
 
627
        pattern.
 
628
        """
 
629
        import glob
 
630
        path = self.path[-1] == '/' and self.path + pattern or slash.join([self.path, pattern])
 
631
        return map(self.clonePath, glob.glob(path))
 
632
 
 
633
    def basename(self):
 
634
        return basename(self.path)
 
635
 
 
636
    def dirname(self):
 
637
        return dirname(self.path)
 
638
 
 
639
    def parent(self):
 
640
        return self.clonePath(self.dirname())
 
641
 
 
642
    def setContent(self, content, ext='.new'):
 
643
        sib = self.siblingExtension(ext)
 
644
        f = sib.open('w')
 
645
        f.write(content)
 
646
        f.close()
 
647
        if platform.isWindows() and exists(self.path):
 
648
            os.unlink(self.path)
 
649
        os.rename(sib.path, self.path)
 
650
 
 
651
    # new in 2.2.0
 
652
 
 
653
    def __cmp__(self, other):
 
654
        if not isinstance(other, FilePath):
 
655
            return NotImplemented
 
656
        return cmp(self.path, other.path)
 
657
 
 
658
    def createDirectory(self):
 
659
        os.mkdir(self.path)
 
660
 
 
661
    def requireCreate(self, val=1):
 
662
        self.alwaysCreate = val
 
663
 
 
664
    def create(self):
 
665
        """Exclusively create a file, only if this file previously did not exist.
 
666
        """
 
667
        fdint = os.open(self.path, (os.O_EXCL |
 
668
                                    os.O_CREAT |
 
669
                                    os.O_RDWR))
 
670
 
 
671
        # XXX TODO: 'name' attribute of returned files is not mutable or
 
672
        # settable via fdopen, so this file is slighly less functional than the
 
673
        # one returned from 'open' by default.  send a patch to Python...
 
674
 
 
675
        return os.fdopen(fdint, 'w+b')
 
676
 
 
677
    def temporarySibling(self):
 
678
        """
 
679
        Create a path naming a temporary sibling of this path in a secure fashion.
 
680
        """
 
681
        sib = self.sibling(_secureEnoughString() + self.basename())
 
682
        sib.requireCreate()
 
683
        return sib
 
684
 
 
685
    _chunkSize = 2 ** 2 ** 2 ** 2
 
686
 
 
687
 
 
688
    def copyTo(self, destination, followLinks=True):
 
689
        """
 
690
        Copies self to destination.
 
691
 
 
692
        If self is a directory, this method copies its children (but not
 
693
        itself) recursively to destination - if destination does not exist as a
 
694
        directory, this method creates it.  If destination is a file, an
 
695
        IOError will be raised.
 
696
 
 
697
        If self is a file, this method copies it to destination.  If
 
698
        destination is a file, this method overwrites it.  If destination is a
 
699
        directory, an IOError will be raised.
 
700
 
 
701
        If self is a link (and followLinks is False), self will be copied
 
702
        over as a new symlink with the same target as returned by os.readlink.
 
703
        That means that if it is absolute, both the old and new symlink will
 
704
        link to the same thing.  If it's relative, then perhaps not (and
 
705
        it's also possible that this relative link will be broken).
 
706
 
 
707
        File/directory permissions and ownership will NOT be copied over.
 
708
 
 
709
        If followLinks is True, symlinks are followed so that they're treated
 
710
        as their targets.  In other words, if self is a link, the link's target
 
711
        will be copied.  If destination is a link, self will be copied to the
 
712
        destination's target (the actual destination will be destination's
 
713
        target).  Symlinks under self (if self is a directory) will be
 
714
        followed and its target's children be copied recursively.
 
715
 
 
716
        If followLinks is False, symlinks will be copied over as symlinks.
 
717
 
 
718
        @param destination: the destination (a FilePath) to which self
 
719
            should be copied
 
720
        @param followLinks: whether symlinks in self should be treated as links
 
721
            or as their targets
 
722
        """
 
723
        if self.islink() and not followLinks:
 
724
            os.symlink(os.readlink(self.path), destination.path)
 
725
            return
 
726
        # XXX TODO: *thorough* audit and documentation of the exact desired
 
727
        # semantics of this code.  Right now the behavior of existent
 
728
        # destination symlinks is convenient, and quite possibly correct, but
 
729
        # its security properties need to be explained.
 
730
        if self.isdir():
 
731
            if not destination.exists():
 
732
                destination.createDirectory()
 
733
            for child in self.children():
 
734
                destChild = destination.child(child.basename())
 
735
                child.copyTo(destChild, followLinks)
 
736
        elif self.isfile():
 
737
            writefile = destination.open('w')
 
738
            readfile = self.open()
 
739
            while 1:
 
740
                # XXX TODO: optionally use os.open, os.read and O_DIRECT and
 
741
                # use os.fstatvfs to determine chunk sizes and make
 
742
                # *****sure**** copy is page-atomic; the following is good
 
743
                # enough for 99.9% of everybody and won't take a week to audit
 
744
                # though.
 
745
                chunk = readfile.read(self._chunkSize)
 
746
                writefile.write(chunk)
 
747
                if len(chunk) < self._chunkSize:
 
748
                    break
 
749
            writefile.close()
 
750
            readfile.close()
 
751
        else:
 
752
            # If you see the following message because you want to copy
 
753
            # symlinks, fifos, block devices, character devices, or unix
 
754
            # sockets, please feel free to add support to do sensible things in
 
755
            # reaction to those types!
 
756
            raise NotImplementedError(
 
757
                "Only copying of files and directories supported")
 
758
 
 
759
 
 
760
    def moveTo(self, destination, followLinks=True):
 
761
        """
 
762
        Move self to destination - basically renaming self to whatever
 
763
        destination is named.  If destination is an already-existing directory,
 
764
        moves all children to destination if destination is empty.  If
 
765
        destination is a non-empty directory, or destination is a file, an
 
766
        OSError will be raised.
 
767
 
 
768
        If moving between filesystems, self needs to be copied, and everything
 
769
        that applies to copyTo applies to moveTo.
 
770
 
 
771
        @param destination: the destination (a FilePath) to which self
 
772
            should be copied
 
773
        @param followLinks: whether symlinks in self should be treated as links
 
774
            or as their targets (only applicable when moving between
 
775
            filesystems)
 
776
        """
 
777
        try:
 
778
            os.rename(self.path, destination.path)
 
779
            self.restat(False)
 
780
        except OSError, ose:
 
781
            if ose.errno == errno.EXDEV:
 
782
                # man 2 rename, ubuntu linux 5.10 "breezy":
 
783
 
 
784
                #   oldpath and newpath are not on the same mounted filesystem.
 
785
                #   (Linux permits a filesystem to be mounted at multiple
 
786
                #   points, but rename(2) does not work across different mount
 
787
                #   points, even if the same filesystem is mounted on both.)
 
788
 
 
789
                # that means it's time to copy trees of directories!
 
790
                secsib = destination.temporarySibling()
 
791
                self.copyTo(secsib, followLinks) # slow
 
792
                secsib.moveTo(destination, followLinks) # visible
 
793
 
 
794
                # done creating new stuff.  let's clean me up.
 
795
                mysecsib = self.temporarySibling()
 
796
                self.moveTo(mysecsib, followLinks) # visible
 
797
                mysecsib.remove() # slow
 
798
            else:
 
799
                raise
 
800
 
 
801
 
 
802
FilePath.clonePath = FilePath