]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/fileutil.py
6449ff1ee8030afef4b531c60d0e53edac64750b
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / fileutil.py
1 """
2 Futz with files like a pro.
3 """
4
5 import sys, exceptions, os, stat, tempfile, time, binascii
6 from collections import namedtuple
7 from errno import ENOENT
8
9 from twisted.python import log
10
11 from pycryptopp.cipher.aes import AES
12
13
14 def rename(src, dst, tries=4, basedelay=0.1):
15     """ Here is a superkludge to workaround the fact that occasionally on
16     Windows some other process (e.g. an anti-virus scanner, a local search
17     engine, etc.) is looking at your file when you want to delete or move it,
18     and hence you can't.  The horrible workaround is to sit and spin, trying
19     to delete it, for a short time and then give up.
20
21     With the default values of tries and basedelay this can block for less
22     than a second.
23
24     @param tries: number of tries -- each time after the first we wait twice
25     as long as the previous wait
26     @param basedelay: how long to wait before the second try
27     """
28     for i in range(tries-1):
29         try:
30             return os.rename(src, dst)
31         except EnvironmentError, le:
32             # XXX Tighten this to check if this is a permission denied error (possibly due to another Windows process having the file open and execute the superkludge only in this case.
33             log.msg("XXX KLUDGE Attempting to move file %s => %s; got %s; sleeping %s seconds" % (src, dst, le, basedelay,))
34             time.sleep(basedelay)
35             basedelay *= 2
36     return os.rename(src, dst) # The last try.
37
38 def remove(f, tries=4, basedelay=0.1):
39     """ Here is a superkludge to workaround the fact that occasionally on
40     Windows some other process (e.g. an anti-virus scanner, a local search
41     engine, etc.) is looking at your file when you want to delete or move it,
42     and hence you can't.  The horrible workaround is to sit and spin, trying
43     to delete it, for a short time and then give up.
44
45     With the default values of tries and basedelay this can block for less
46     than a second.
47
48     @param tries: number of tries -- each time after the first we wait twice
49     as long as the previous wait
50     @param basedelay: how long to wait before the second try
51     """
52     try:
53         os.chmod(f, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
54     except:
55         pass
56     for i in range(tries-1):
57         try:
58             return os.remove(f)
59         except EnvironmentError, le:
60             # XXX Tighten this to check if this is a permission denied error (possibly due to another Windows process having the file open and execute the superkludge only in this case.
61             if not os.path.exists(f):
62                 return
63             log.msg("XXX KLUDGE Attempting to remove file %s; got %s; sleeping %s seconds" % (f, le, basedelay,))
64             time.sleep(basedelay)
65             basedelay *= 2
66     return os.remove(f) # The last try.
67
68 class ReopenableNamedTemporaryFile:
69     """
70     This uses tempfile.mkstemp() to generate a secure temp file.  It then closes
71     the file, leaving a zero-length file as a placeholder.  You can get the
72     filename with ReopenableNamedTemporaryFile.name.  When the
73     ReopenableNamedTemporaryFile instance is garbage collected or its shutdown()
74     method is called, it deletes the file.
75     """
76     def __init__(self, *args, **kwargs):
77         fd, self.name = tempfile.mkstemp(*args, **kwargs)
78         os.close(fd)
79
80     def __repr__(self):
81         return "<%s instance at %x %s>" % (self.__class__.__name__, id(self), self.name)
82
83     def __str__(self):
84         return self.__repr__()
85
86     def __del__(self):
87         self.shutdown()
88
89     def shutdown(self):
90         remove(self.name)
91
92 class EncryptedTemporaryFile:
93     # not implemented: next, readline, readlines, xreadlines, writelines
94
95     def __init__(self):
96         self.file = tempfile.TemporaryFile()
97         self.key = os.urandom(16)  # AES-128
98
99     def _crypt(self, offset, data):
100         offset_big = offset // 16
101         offset_small = offset % 16
102         iv = binascii.unhexlify("%032x" % offset_big)
103         cipher = AES(self.key, iv=iv)
104         cipher.process("\x00"*offset_small)
105         return cipher.process(data)
106
107     def close(self):
108         self.file.close()
109
110     def flush(self):
111         self.file.flush()
112
113     def seek(self, offset, whence=0):  # 0 = SEEK_SET
114         self.file.seek(offset, whence)
115
116     def tell(self):
117         offset = self.file.tell()
118         return offset
119
120     def read(self, size=-1):
121         """A read must not follow a write, or vice-versa, without an intervening seek."""
122         index = self.file.tell()
123         ciphertext = self.file.read(size)
124         plaintext = self._crypt(index, ciphertext)
125         return plaintext
126
127     def write(self, plaintext):
128         """A read must not follow a write, or vice-versa, without an intervening seek.
129         If seeking and then writing causes a 'hole' in the file, the contents of the
130         hole are unspecified."""
131         index = self.file.tell()
132         ciphertext = self._crypt(index, plaintext)
133         self.file.write(ciphertext)
134
135     def truncate(self, newsize):
136         """Truncate or extend the file to 'newsize'. If it is extended, the contents after the
137         old end-of-file are unspecified. The file position after this operation is unspecified."""
138         self.file.truncate(newsize)
139
140
141 def make_dirs(dirname, mode=0777):
142     """
143     An idempotent version of os.makedirs().  If the dir already exists, do
144     nothing and return without raising an exception.  If this call creates the
145     dir, return without raising an exception.  If there is an error that
146     prevents creation or if the directory gets deleted after make_dirs() creates
147     it and before make_dirs() checks that it exists, raise an exception.
148     """
149     tx = None
150     try:
151         os.makedirs(dirname, mode)
152     except OSError, x:
153         tx = x
154
155     if not os.path.isdir(dirname):
156         if tx:
157             raise tx
158         raise exceptions.IOError, "unknown error prevented creation of directory, or deleted the directory immediately after creation: %s" % dirname # careful not to construct an IOError with a 2-tuple, as that has a special meaning...
159
160 def rm_dir(dirname):
161     """
162     A threadsafe and idempotent version of shutil.rmtree().  If the dir is
163     already gone, do nothing and return without raising an exception.  If this
164     call removes the dir, return without raising an exception.  If there is an
165     error that prevents deletion or if the directory gets created again after
166     rm_dir() deletes it and before rm_dir() checks that it is gone, raise an
167     exception.
168     """
169     excs = []
170     try:
171         os.chmod(dirname, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
172         for f in os.listdir(dirname):
173             fullname = os.path.join(dirname, f)
174             if os.path.isdir(fullname):
175                 rm_dir(fullname)
176             else:
177                 remove(fullname)
178         os.rmdir(dirname)
179     except Exception, le:
180         # Ignore "No such file or directory"
181         if (not isinstance(le, OSError)) or le.args[0] != 2:
182             excs.append(le)
183
184     # Okay, now we've recursively removed everything, ignoring any "No
185     # such file or directory" errors, and collecting any other errors.
186
187     if os.path.exists(dirname):
188         if len(excs) == 1:
189             raise excs[0]
190         if len(excs) == 0:
191             raise OSError, "Failed to remove dir for unknown reason."
192         raise OSError, excs
193
194
195 def remove_if_possible(f):
196     try:
197         remove(f)
198     except:
199         pass
200
201 def du(basedir):
202     size = 0
203
204     for root, dirs, files in os.walk(basedir):
205         for f in files:
206             fn = os.path.join(root, f)
207             size += os.path.getsize(fn)
208
209     return size
210
211 def move_into_place(source, dest):
212     """Atomically replace a file, or as near to it as the platform allows.
213     The dest file may or may not exist."""
214     if "win32" in sys.platform.lower():
215         remove_if_possible(dest)
216     os.rename(source, dest)
217
218 def write_atomically(target, contents, mode="b"):
219     f = open(target+".tmp", "w"+mode)
220     try:
221         f.write(contents)
222     finally:
223         f.close()
224     move_into_place(target+".tmp", target)
225
226 def write(path, data, mode="wb"):
227     wf = open(path, mode)
228     try:
229         wf.write(data)
230     finally:
231         wf.close()
232
233 def read(path):
234     rf = open(path, "rb")
235     try:
236         return rf.read()
237     finally:
238         rf.close()
239
240 def put_file(path, inf):
241     precondition_abspath(path)
242
243     # TODO: create temporary file and move into place?
244     outf = open(path, "wb")
245     try:
246         while True:
247             data = inf.read(32768)
248             if not data:
249                 break
250             outf.write(data)
251     finally:
252         outf.close()
253
254
255 def precondition_abspath(path):
256     if not isinstance(path, unicode):
257         raise AssertionError("an abspath must be a Unicode string")
258
259     if sys.platform == "win32":
260         # This intentionally doesn't view absolute paths starting with a drive specification, or
261         # paths relative to the current drive, as acceptable.
262         if not path.startswith("\\\\"):
263             raise AssertionError("an abspath should be normalized using abspath_expanduser_unicode")
264     else:
265         # This intentionally doesn't view the path '~' or paths starting with '~/' as acceptable.
266         if not os.path.isabs(path):
267             raise AssertionError("an abspath should be normalized using abspath_expanduser_unicode")
268
269 # Work around <http://bugs.python.org/issue3426>. This code is adapted from
270 # <http://svn.python.org/view/python/trunk/Lib/ntpath.py?revision=78247&view=markup>
271 # with some simplifications.
272
273 _getfullpathname = None
274 try:
275     from nt import _getfullpathname
276 except ImportError:
277     pass
278
279 def abspath_expanduser_unicode(path, base=None, long_path=True):
280     """
281     Return the absolute version of a path. If 'base' is given and 'path' is relative,
282     the path will be expanded relative to 'base'.
283     'path' must be a Unicode string. 'base', if given, must be a Unicode string
284     corresponding to an absolute path as returned by a previous call to
285     abspath_expanduser_unicode.
286     On Windows, the result will be a long path unless long_path is given as False.
287     """
288     if not isinstance(path, unicode):
289         raise AssertionError("paths must be Unicode strings")
290     if base is not None:
291         precondition_abspath(base)
292
293     path = expanduser(path)
294
295     if _getfullpathname:
296         # On Windows, os.path.isabs will incorrectly return True
297         # for paths without a drive letter (that are not UNC paths),
298         # e.g. "\\". See <http://bugs.python.org/issue1669539>.
299         try:
300             if base is None:
301                 path = _getfullpathname(path or u".")
302             else:
303                 path = _getfullpathname(os.path.join(base, path))
304         except OSError:
305             pass
306
307     if not os.path.isabs(path):
308         if base is None:
309             path = os.path.join(os.getcwdu(), path)
310         else:
311             path = os.path.join(base, path)
312
313     # We won't hit <http://bugs.python.org/issue5827> because
314     # there is always at least one Unicode path component.
315     path = os.path.normpath(path)
316
317     if sys.platform == "win32" and long_path:
318         path = to_windows_long_path(path)
319
320     return path
321
322 def to_windows_long_path(path):
323     # '/' is normally a perfectly valid path component separator in Windows.
324     # However, when using the "\\?\" syntax it is not recognized, so we
325     # replace it with '\' here.
326     path = path.replace(u"/", u"\\")
327
328     # Note that other normalizations such as removing '.' and '..' should
329     # be done outside this function.
330
331     if path.startswith(u"\\\\?\\") or path.startswith(u"\\\\.\\"):
332         return path
333     elif path.startswith(u"\\\\"):
334         return u"\\\\?\\UNC\\" + path[2 :]
335     else:
336         return u"\\\\?\\" + path
337
338
339 have_GetDiskFreeSpaceExW = False
340 if sys.platform == "win32":
341     from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_ulonglong, create_unicode_buffer, \
342         get_last_error
343     from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR
344
345     # <http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188%28v=vs.85%29.aspx>
346     GetEnvironmentVariableW = WINFUNCTYPE(
347         DWORD,
348           LPCWSTR, LPWSTR, DWORD,
349         use_last_error=True
350       )(("GetEnvironmentVariableW", windll.kernel32))
351
352     try:
353         # <http://msdn.microsoft.com/en-us/library/aa383742%28v=VS.85%29.aspx>
354         PULARGE_INTEGER = POINTER(c_ulonglong)
355
356         # <http://msdn.microsoft.com/en-us/library/aa364937%28VS.85%29.aspx>
357         GetDiskFreeSpaceExW = WINFUNCTYPE(
358             BOOL,
359               LPCWSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER,
360             use_last_error=True
361           )(("GetDiskFreeSpaceExW", windll.kernel32))
362
363         have_GetDiskFreeSpaceExW = True
364     except Exception:
365         import traceback
366         traceback.print_exc()
367
368 def expanduser(path):
369     # os.path.expanduser is hopelessly broken for Unicode paths on Windows (ticket #1674).
370     if sys.platform == "win32":
371         return windows_expanduser(path)
372     else:
373         return os.path.expanduser(path)
374
375 def windows_expanduser(path):
376     if not path.startswith('~'):
377         return path
378
379     home_dir = windows_getenv(u'USERPROFILE')
380     if home_dir is None:
381         home_drive = windows_getenv(u'HOMEDRIVE')
382         home_path = windows_getenv(u'HOMEPATH')
383         if home_drive is None or home_path is None:
384             raise OSError("Could not find home directory: neither %USERPROFILE% nor (%HOMEDRIVE% and %HOMEPATH%) are set.")
385         home_dir = os.path.join(home_drive, home_path)
386
387     if path == '~':
388         return home_dir
389     elif path.startswith('~/') or path.startswith('~\\'):
390         return os.path.join(home_dir, path[2 :])
391     else:
392         return path
393
394 # <https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx>
395 ERROR_ENVVAR_NOT_FOUND = 203
396
397 def windows_getenv(name):
398     # Based on <http://stackoverflow.com/questions/2608200/problems-with-umlauts-in-python-appdata-environvent-variable/2608368#2608368>,
399     # with improved error handling. Returns None if there is no enivronment variable of the given name.
400     if not isinstance(name, unicode):
401         raise AssertionError("name must be Unicode")
402
403     n = GetEnvironmentVariableW(name, None, 0)
404     # GetEnvironmentVariableW returns DWORD, so n cannot be negative.
405     if n == 0:
406         err = get_last_error()
407         if err == ERROR_ENVVAR_NOT_FOUND:
408             return None
409         raise OSError("Windows error %d attempting to read size of environment variable %r"
410                       % (err, name))
411     if n == 1:
412         # Avoid an ambiguity between a zero-length string and an error in the return value of the
413         # call to GetEnvironmentVariableW below.
414         return u""
415
416     buf = create_unicode_buffer(u'\0'*n)
417     retval = GetEnvironmentVariableW(name, buf, n)
418     if retval == 0:
419         err = get_last_error()
420         if err == ERROR_ENVVAR_NOT_FOUND:
421             return None
422         raise OSError("Windows error %d attempting to read environment variable %r"
423                       % (err, name))
424     if retval >= n:
425         raise OSError("Unexpected result %d (expected less than %d) from GetEnvironmentVariableW attempting to read environment variable %r"
426                       % (retval, n, name))
427
428     return buf.value
429
430 def get_disk_stats(whichdir, reserved_space=0):
431     """Return disk statistics for the storage disk, in the form of a dict
432     with the following fields.
433       total:            total bytes on disk
434       free_for_root:    bytes actually free on disk
435       free_for_nonroot: bytes free for "a non-privileged user" [Unix] or
436                           the current user [Windows]; might take into
437                           account quotas depending on platform
438       used:             bytes used on disk
439       avail:            bytes available excluding reserved space
440     An AttributeError can occur if the OS has no API to get disk information.
441     An EnvironmentError can occur if the OS call fails.
442
443     whichdir is a directory on the filesystem in question -- the
444     answer is about the filesystem, not about the directory, so the
445     directory is used only to specify which filesystem.
446
447     reserved_space is how many bytes to subtract from the answer, so
448     you can pass how many bytes you would like to leave unused on this
449     filesystem as reserved_space.
450     """
451
452     if have_GetDiskFreeSpaceExW:
453         # If this is a Windows system and GetDiskFreeSpaceExW is available, use it.
454         # (This might put up an error dialog unless
455         # SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX) has been called,
456         # which we do in allmydata.windows.fixups.initialize().)
457
458         n_free_for_nonroot = c_ulonglong(0)
459         n_total            = c_ulonglong(0)
460         n_free_for_root    = c_ulonglong(0)
461         retval = GetDiskFreeSpaceExW(whichdir, byref(n_free_for_nonroot),
462                                                byref(n_total),
463                                                byref(n_free_for_root))
464         if retval == 0:
465             raise OSError("Windows error %d attempting to get disk statistics for %r"
466                           % (get_last_error(), whichdir))
467         free_for_nonroot = n_free_for_nonroot.value
468         total            = n_total.value
469         free_for_root    = n_free_for_root.value
470     else:
471         # For Unix-like systems.
472         # <http://docs.python.org/library/os.html#os.statvfs>
473         # <http://opengroup.org/onlinepubs/7990989799/xsh/fstatvfs.html>
474         # <http://opengroup.org/onlinepubs/7990989799/xsh/sysstatvfs.h.html>
475         s = os.statvfs(whichdir)
476
477         # on my mac laptop:
478         #  statvfs(2) is a wrapper around statfs(2).
479         #    statvfs.f_frsize = statfs.f_bsize :
480         #     "minimum unit of allocation" (statvfs)
481         #     "fundamental file system block size" (statfs)
482         #    statvfs.f_bsize = statfs.f_iosize = stat.st_blocks : preferred IO size
483         # on an encrypted home directory ("FileVault"), it gets f_blocks
484         # wrong, and s.f_blocks*s.f_frsize is twice the size of my disk,
485         # but s.f_bavail*s.f_frsize is correct
486
487         total = s.f_frsize * s.f_blocks
488         free_for_root = s.f_frsize * s.f_bfree
489         free_for_nonroot = s.f_frsize * s.f_bavail
490
491     # valid for all platforms:
492     used = total - free_for_root
493     avail = max(free_for_nonroot - reserved_space, 0)
494
495     return { 'total': total,
496              'free_for_root': free_for_root,
497              'free_for_nonroot': free_for_nonroot,
498              'used': used,
499              'avail': avail,
500            }
501
502 def get_available_space(whichdir, reserved_space):
503     """Returns available space for share storage in bytes, or None if no
504     API to get this information is available.
505
506     whichdir is a directory on the filesystem in question -- the
507     answer is about the filesystem, not about the directory, so the
508     directory is used only to specify which filesystem.
509
510     reserved_space is how many bytes to subtract from the answer, so
511     you can pass how many bytes you would like to leave unused on this
512     filesystem as reserved_space.
513     """
514     try:
515         return get_disk_stats(whichdir, reserved_space)['avail']
516     except AttributeError:
517         return None
518     except EnvironmentError:
519         log.msg("OS call to get disk statistics failed")
520         return 0
521
522
523 if sys.platform == "win32":
524     from ctypes.wintypes import BOOL, HANDLE, DWORD, LPCWSTR, LPVOID, WinError, get_last_error
525
526     # <http://msdn.microsoft.com/en-us/library/aa363858%28v=vs.85%29.aspx>
527     CreateFileW = WINFUNCTYPE(HANDLE, LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE) \
528                       (("CreateFileW", windll.kernel32))
529
530     GENERIC_WRITE        = 0x40000000
531     FILE_SHARE_READ      = 0x00000001
532     FILE_SHARE_WRITE     = 0x00000002
533     OPEN_EXISTING        = 3
534     INVALID_HANDLE_VALUE = 0xFFFFFFFF
535
536     # <http://msdn.microsoft.com/en-us/library/aa364439%28v=vs.85%29.aspx>
537     FlushFileBuffers = WINFUNCTYPE(BOOL, HANDLE)(("FlushFileBuffers", windll.kernel32))
538
539     # <http://msdn.microsoft.com/en-us/library/ms724211%28v=vs.85%29.aspx>
540     CloseHandle = WINFUNCTYPE(BOOL, HANDLE)(("CloseHandle", windll.kernel32))
541
542     # <http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/4465cafb-f4ed-434f-89d8-c85ced6ffaa8/>
543     def flush_volume(path):
544         drive = os.path.splitdrive(os.path.realpath(path))[0]
545
546         hVolume = CreateFileW(u"\\\\.\\" + drive,
547                               GENERIC_WRITE,
548                               FILE_SHARE_READ | FILE_SHARE_WRITE,
549                               None,
550                               OPEN_EXISTING,
551                               0,
552                               None
553                              )
554         if hVolume == INVALID_HANDLE_VALUE:
555             raise WinError()
556
557         if FlushFileBuffers(hVolume) == 0:
558             raise WinError()
559
560         CloseHandle(hVolume)
561 else:
562     def flush_volume(path):
563         # use sync()?
564         pass
565
566
567 class ConflictError(Exception):
568     pass
569
570 class UnableToUnlinkReplacementError(Exception):
571     pass
572
573 def reraise(wrapper):
574     _, exc, tb = sys.exc_info()
575     wrapper_exc = wrapper("%s: %s" % (exc.__class__.__name__, exc))
576     raise wrapper_exc.__class__, wrapper_exc, tb
577
578 if sys.platform == "win32":
579     from ctypes import WINFUNCTYPE, windll, WinError, get_last_error
580     from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPVOID
581
582     # <https://msdn.microsoft.com/en-us/library/windows/desktop/aa365512%28v=vs.85%29.aspx>
583     ReplaceFileW = WINFUNCTYPE(
584         BOOL,
585           LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID,
586         use_last_error=True
587       )(("ReplaceFileW", windll.kernel32))
588
589     REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002
590
591     def rename_no_overwrite(source_path, dest_path):
592         os.rename(source_path, dest_path)
593
594     def replace_file(replaced_path, replacement_path, backup_path):
595         precondition_abspath(replaced_path)
596         precondition_abspath(replacement_path)
597         precondition_abspath(backup_path)
598
599         r = ReplaceFileW(replaced_path, replacement_path, backup_path,
600                          REPLACEFILE_IGNORE_MERGE_ERRORS, None, None)
601         if r == 0:
602             # The UnableToUnlinkReplacementError case does not happen on Windows;
603             # all errors should be treated as signalling a conflict.
604             err = get_last_error()
605             raise ConflictError("WinError: %s" % (WinError(err)))
606 else:
607     def rename_no_overwrite(source_path, dest_path):
608         # link will fail with EEXIST if there is already something at dest_path.
609         os.link(source_path, dest_path)
610         try:
611             os.unlink(source_path)
612         except EnvironmentError:
613             reraise(UnableToUnlinkReplacementError)
614
615     def replace_file(replaced_path, replacement_path, backup_path):
616         precondition_abspath(replaced_path)
617         precondition_abspath(replacement_path)
618         precondition_abspath(backup_path)
619
620         if not os.path.exists(replacement_path):
621             raise ConflictError("Replacement file not found: %r" % (replacement_path,))
622
623         try:
624             os.rename(replaced_path, backup_path)
625         except OSError as e:
626             if e.errno != ENOENT:
627                 raise
628         try:
629             rename_no_overwrite(replacement_path, replaced_path)
630         except EnvironmentError:
631             reraise(ConflictError)
632
633 PathInfo = namedtuple('PathInfo', 'isdir isfile islink exists size mtime ctime')
634
635 def get_pathinfo(path_u, now=None):
636     try:
637         statinfo = os.lstat(path_u)
638         mode = statinfo.st_mode
639         return PathInfo(isdir =stat.S_ISDIR(mode),
640                         isfile=stat.S_ISREG(mode),
641                         islink=stat.S_ISLNK(mode),
642                         exists=True,
643                         size  =statinfo.st_size,
644                         mtime =statinfo.st_mtime,
645                         ctime =statinfo.st_ctime,
646                        )
647     except OSError as e:
648         if e.errno == ENOENT:
649             if now is None:
650                 now = time.time()
651             return PathInfo(isdir =False,
652                             isfile=False,
653                             islink=False,
654                             exists=False,
655                             size  =None,
656                             mtime =now,
657                             ctime =now,
658                            )
659         raise