]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/fileutil.py
8194fcd4dc68c463c472cb687833a7ccea556945
[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):
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     """
287     if not isinstance(path, unicode):
288         raise AssertionError("paths must be Unicode strings")
289     if base is not None:
290         precondition_abspath(base)
291
292     path = expanduser(path)
293
294     if _getfullpathname:
295         # On Windows, os.path.isabs will incorrectly return True
296         # for paths without a drive letter (that are not UNC paths),
297         # e.g. "\\". See <http://bugs.python.org/issue1669539>.
298         try:
299             if base is None:
300                 path = _getfullpathname(path or u".")
301             else:
302                 path = _getfullpathname(os.path.join(base, path))
303         except OSError:
304             pass
305
306     if not os.path.isabs(path):
307         if base is None:
308             path = os.path.join(os.getcwdu(), path)
309         else:
310             path = os.path.join(base, path)
311
312     # We won't hit <http://bugs.python.org/issue5827> because
313     # there is always at least one Unicode path component.
314     path = os.path.normpath(path)
315
316     if sys.platform == "win32":
317         path = to_windows_long_path(path)
318
319     return path
320
321 def to_windows_long_path(path):
322     # '/' is normally a perfectly valid path component separator in Windows.
323     # However, when using the "\\?\" syntax it is not recognized, so we
324     # replace it with '\' here.
325     path = path.replace(u"/", u"\\")
326
327     # Note that other normalizations such as removing '.' and '..' should
328     # be done outside this function.
329
330     if path.startswith(u"\\\\?\\") or path.startswith(u"\\\\.\\"):
331         return path
332     elif path.startswith(u"\\\\"):
333         return u"\\\\?\\UNC\\" + path[2 :]
334     else:
335         return u"\\\\?\\" + path
336
337
338 have_GetDiskFreeSpaceExW = False
339 if sys.platform == "win32":
340     from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_ulonglong, create_unicode_buffer, \
341         get_last_error
342     from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR
343
344     # <http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188%28v=vs.85%29.aspx>
345     GetEnvironmentVariableW = WINFUNCTYPE(
346         DWORD,
347           LPCWSTR, LPWSTR, DWORD,
348         use_last_error=True
349       )(("GetEnvironmentVariableW", windll.kernel32))
350
351     try:
352         # <http://msdn.microsoft.com/en-us/library/aa383742%28v=VS.85%29.aspx>
353         PULARGE_INTEGER = POINTER(c_ulonglong)
354
355         # <http://msdn.microsoft.com/en-us/library/aa364937%28VS.85%29.aspx>
356         GetDiskFreeSpaceExW = WINFUNCTYPE(
357             BOOL,
358               LPCWSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER,
359             use_last_error=True
360           )(("GetDiskFreeSpaceExW", windll.kernel32))
361
362         have_GetDiskFreeSpaceExW = True
363     except Exception:
364         import traceback
365         traceback.print_exc()
366
367 def expanduser(path):
368     # os.path.expanduser is hopelessly broken for Unicode paths on Windows (ticket #1674).
369     if sys.platform == "win32":
370         return windows_expanduser(path)
371     else:
372         return os.path.expanduser(path)
373
374 def windows_expanduser(path):
375     if not path.startswith('~'):
376         return path
377
378     home_dir = windows_getenv(u'USERPROFILE')
379     if home_dir is None:
380         home_drive = windows_getenv(u'HOMEDRIVE')
381         home_path = windows_getenv(u'HOMEPATH')
382         if home_drive is None or home_path is None:
383             raise OSError("Could not find home directory: neither %USERPROFILE% nor (%HOMEDRIVE% and %HOMEPATH%) are set.")
384         home_dir = os.path.join(home_drive, home_path)
385
386     if path == '~':
387         return home_dir
388     elif path.startswith('~/') or path.startswith('~\\'):
389         return os.path.join(home_dir, path[2 :])
390     else:
391         return path
392
393 # <https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx>
394 ERROR_ENVVAR_NOT_FOUND = 203
395
396 def windows_getenv(name):
397     # Based on <http://stackoverflow.com/questions/2608200/problems-with-umlauts-in-python-appdata-environvent-variable/2608368#2608368>,
398     # with improved error handling. Returns None if there is no enivronment variable of the given name.
399     if not isinstance(name, unicode):
400         raise AssertionError("name must be Unicode")
401
402     n = GetEnvironmentVariableW(name, None, 0)
403     # GetEnvironmentVariableW returns DWORD, so n cannot be negative.
404     if n == 0:
405         err = get_last_error()
406         if err == ERROR_ENVVAR_NOT_FOUND:
407             return None
408         raise OSError("Windows error %d attempting to read size of environment variable %r"
409                       % (err, name))
410     if n == 1:
411         # Avoid an ambiguity between a zero-length string and an error in the return value of the
412         # call to GetEnvironmentVariableW below.
413         return u""
414
415     buf = create_unicode_buffer(u'\0'*n)
416     retval = GetEnvironmentVariableW(name, buf, n)
417     if retval == 0:
418         err = get_last_error()
419         if err == ERROR_ENVVAR_NOT_FOUND:
420             return None
421         raise OSError("Windows error %d attempting to read environment variable %r"
422                       % (err, name))
423     if retval >= n:
424         raise OSError("Unexpected result %d (expected less than %d) from GetEnvironmentVariableW attempting to read environment variable %r"
425                       % (retval, n, name))
426
427     return buf.value
428
429 def get_disk_stats(whichdir, reserved_space=0):
430     """Return disk statistics for the storage disk, in the form of a dict
431     with the following fields.
432       total:            total bytes on disk
433       free_for_root:    bytes actually free on disk
434       free_for_nonroot: bytes free for "a non-privileged user" [Unix] or
435                           the current user [Windows]; might take into
436                           account quotas depending on platform
437       used:             bytes used on disk
438       avail:            bytes available excluding reserved space
439     An AttributeError can occur if the OS has no API to get disk information.
440     An EnvironmentError can occur if the OS call fails.
441
442     whichdir is a directory on the filesystem in question -- the
443     answer is about the filesystem, not about the directory, so the
444     directory is used only to specify which filesystem.
445
446     reserved_space is how many bytes to subtract from the answer, so
447     you can pass how many bytes you would like to leave unused on this
448     filesystem as reserved_space.
449     """
450
451     if have_GetDiskFreeSpaceExW:
452         # If this is a Windows system and GetDiskFreeSpaceExW is available, use it.
453         # (This might put up an error dialog unless
454         # SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX) has been called,
455         # which we do in allmydata.windows.fixups.initialize().)
456
457         n_free_for_nonroot = c_ulonglong(0)
458         n_total            = c_ulonglong(0)
459         n_free_for_root    = c_ulonglong(0)
460         retval = GetDiskFreeSpaceExW(whichdir, byref(n_free_for_nonroot),
461                                                byref(n_total),
462                                                byref(n_free_for_root))
463         if retval == 0:
464             raise OSError("Windows error %d attempting to get disk statistics for %r"
465                           % (get_last_error(), whichdir))
466         free_for_nonroot = n_free_for_nonroot.value
467         total            = n_total.value
468         free_for_root    = n_free_for_root.value
469     else:
470         # For Unix-like systems.
471         # <http://docs.python.org/library/os.html#os.statvfs>
472         # <http://opengroup.org/onlinepubs/7990989799/xsh/fstatvfs.html>
473         # <http://opengroup.org/onlinepubs/7990989799/xsh/sysstatvfs.h.html>
474         s = os.statvfs(whichdir)
475
476         # on my mac laptop:
477         #  statvfs(2) is a wrapper around statfs(2).
478         #    statvfs.f_frsize = statfs.f_bsize :
479         #     "minimum unit of allocation" (statvfs)
480         #     "fundamental file system block size" (statfs)
481         #    statvfs.f_bsize = statfs.f_iosize = stat.st_blocks : preferred IO size
482         # on an encrypted home directory ("FileVault"), it gets f_blocks
483         # wrong, and s.f_blocks*s.f_frsize is twice the size of my disk,
484         # but s.f_bavail*s.f_frsize is correct
485
486         total = s.f_frsize * s.f_blocks
487         free_for_root = s.f_frsize * s.f_bfree
488         free_for_nonroot = s.f_frsize * s.f_bavail
489
490     # valid for all platforms:
491     used = total - free_for_root
492     avail = max(free_for_nonroot - reserved_space, 0)
493
494     return { 'total': total,
495              'free_for_root': free_for_root,
496              'free_for_nonroot': free_for_nonroot,
497              'used': used,
498              'avail': avail,
499            }
500
501 def get_available_space(whichdir, reserved_space):
502     """Returns available space for share storage in bytes, or None if no
503     API to get this information is available.
504
505     whichdir is a directory on the filesystem in question -- the
506     answer is about the filesystem, not about the directory, so the
507     directory is used only to specify which filesystem.
508
509     reserved_space is how many bytes to subtract from the answer, so
510     you can pass how many bytes you would like to leave unused on this
511     filesystem as reserved_space.
512     """
513     try:
514         return get_disk_stats(whichdir, reserved_space)['avail']
515     except AttributeError:
516         return None
517     except EnvironmentError:
518         log.msg("OS call to get disk statistics failed")
519         return 0
520
521
522 if sys.platform == "win32":
523     from ctypes.wintypes import BOOL, HANDLE, DWORD, LPCWSTR, LPVOID, WinError, get_last_error
524
525     # <http://msdn.microsoft.com/en-us/library/aa363858%28v=vs.85%29.aspx>
526     CreateFileW = WINFUNCTYPE(HANDLE, LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE) \
527                       (("CreateFileW", windll.kernel32))
528
529     GENERIC_WRITE        = 0x40000000
530     FILE_SHARE_READ      = 0x00000001
531     FILE_SHARE_WRITE     = 0x00000002
532     OPEN_EXISTING        = 3
533     INVALID_HANDLE_VALUE = 0xFFFFFFFF
534
535     # <http://msdn.microsoft.com/en-us/library/aa364439%28v=vs.85%29.aspx>
536     FlushFileBuffers = WINFUNCTYPE(BOOL, HANDLE)(("FlushFileBuffers", windll.kernel32))
537
538     # <http://msdn.microsoft.com/en-us/library/ms724211%28v=vs.85%29.aspx>
539     CloseHandle = WINFUNCTYPE(BOOL, HANDLE)(("CloseHandle", windll.kernel32))
540
541     # <http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/4465cafb-f4ed-434f-89d8-c85ced6ffaa8/>
542     def flush_volume(path):
543         drive = os.path.splitdrive(os.path.realpath(path))[0]
544
545         hVolume = CreateFileW(u"\\\\.\\" + drive,
546                               GENERIC_WRITE,
547                               FILE_SHARE_READ | FILE_SHARE_WRITE,
548                               None,
549                               OPEN_EXISTING,
550                               0,
551                               None
552                              )
553         if hVolume == INVALID_HANDLE_VALUE:
554             raise WinError()
555
556         if FlushFileBuffers(hVolume) == 0:
557             raise WinError()
558
559         CloseHandle(hVolume)
560 else:
561     def flush_volume(path):
562         # use sync()?
563         pass
564
565
566 class ConflictError(Exception):
567     pass
568
569 class UnableToUnlinkReplacementError(Exception):
570     pass
571
572 def reraise(wrapper):
573     _, exc, tb = sys.exc_info()
574     wrapper_exc = wrapper("%s: %s" % (exc.__class__.__name__, exc))
575     raise wrapper_exc.__class__, wrapper_exc, tb
576
577 if sys.platform == "win32":
578     from ctypes import WINFUNCTYPE, windll, WinError, get_last_error
579     from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPVOID
580
581     # <https://msdn.microsoft.com/en-us/library/windows/desktop/aa365512%28v=vs.85%29.aspx>
582     ReplaceFileW = WINFUNCTYPE(
583         BOOL,
584           LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID,
585         use_last_error=True
586       )(("ReplaceFileW", windll.kernel32))
587
588     REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002
589
590     def rename_no_overwrite(source_path, dest_path):
591         os.rename(source_path, dest_path)
592
593     def replace_file(replaced_path, replacement_path, backup_path):
594         precondition_abspath(replaced_path)
595         precondition_abspath(replacement_path)
596         precondition_abspath(backup_path)
597
598         r = ReplaceFileW(replaced_path, replacement_path, backup_path,
599                          REPLACEFILE_IGNORE_MERGE_ERRORS, None, None)
600         if r == 0:
601             # The UnableToUnlinkReplacementError case does not happen on Windows;
602             # all errors should be treated as signalling a conflict.
603             err = get_last_error()
604             raise ConflictError("WinError: %s" % (WinError(err)))
605 else:
606     def rename_no_overwrite(source_path, dest_path):
607         # link will fail with EEXIST if there is already something at dest_path.
608         os.link(source_path, dest_path)
609         try:
610             os.unlink(source_path)
611         except EnvironmentError:
612             reraise(UnableToUnlinkReplacementError)
613
614     def replace_file(replaced_path, replacement_path, backup_path):
615         precondition_abspath(replaced_path)
616         precondition_abspath(replacement_path)
617         precondition_abspath(backup_path)
618
619         if not os.path.exists(replacement_path):
620             raise ConflictError("Replacement file not found: %r" % (replacement_path,))
621
622         try:
623             os.rename(replaced_path, backup_path)
624         except OSError as e:
625             if e.errno != ENOENT:
626                 raise
627         try:
628             rename_no_overwrite(replacement_path, replaced_path)
629         except EnvironmentError:
630             reraise(ConflictError)
631
632 PathInfo = namedtuple('PathInfo', 'isdir isfile islink exists size mtime ctime')
633
634 def get_pathinfo(path_u, now=None):
635     try:
636         statinfo = os.lstat(path_u)
637         mode = statinfo.st_mode
638         return PathInfo(isdir =stat.S_ISDIR(mode),
639                         isfile=stat.S_ISREG(mode),
640                         islink=stat.S_ISLNK(mode),
641                         exists=True,
642                         size  =statinfo.st_size,
643                         mtime =statinfo.st_mtime,
644                         ctime =statinfo.st_ctime,
645                        )
646     except OSError as e:
647         if e.errno == ENOENT:
648             if now is None:
649                 now = time.time()
650             return PathInfo(isdir =False,
651                             isfile=False,
652                             islink=False,
653                             exists=False,
654                             size  =None,
655                             mtime =now,
656                             ctime =now,
657                            )
658         raise