diff --git a/Doc/library/os.rst b/Doc/library/os.rst --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -810,6 +810,35 @@ Availability: Unix, and Windows. +.. function:: fullfsync(fd) + + The POSIX standart requires that :c:func:`fsync` must transfer the buffered + data to the storage device, not that the data is actually written by the + device itself. It explicitely leaves it up to operating system implementors + whether users are given stronger guarantees on data integrity or not. Some + systems also offer special functions which overtake the part of making such + stronger guarantees, i.e., Mac OS X and NetBSD. + + This non-standart function is *optionally* made available to access such + special functionality when feasible. It will force write of file buffers to + disk and the flush of disk caches of the file given by file descriptor *fd*. + To strive for best-possible data integrity, the following can be done:: + + # Force writeout of local buffer modifications + f.flush() + # Then synchronize the changes to physical backing store + if hasattr(os, 'fullfsync'): + os.fullfsync(f.fileno()) + elif hasattr(os, 'fsync'): + os.fsync(f.fileno()) + + ..note:: + Calling this function may take a long time, since it may block + until the disk reports that the transfer has been completed. + + Availability: Unix. + + .. function:: ftruncate(fd, length) Truncate the file corresponding to file descriptor *fd*, so that it is at most diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -835,12 +835,12 @@ class TestInvalidFD(unittest.TestCase): singles = ["fchdir", "dup", "fdopen", "fdatasync", "fstat", - "fstatvfs", "fsync", "tcgetpgrp", "ttyname"] + "fstatvfs", "fsync", "fullfsync", "tcgetpgrp", "ttyname"] #singles.append("close") - #We omit close because it doesn'r raise an exception on some platforms + # We omit close because it doesn't raise an exception on some platforms def get_single(f): def helper(self): - if hasattr(os, f): + if hasattr(os, f): self.check(getattr(os, f)) return helper for f in singles: diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -174,6 +174,11 @@ #endif /* ! __IBMC__ */ #ifndef _MSC_VER + /* os.fullfsync()? */ +# if (defined HAVE_FSYNC && ((defined __APPLE__ && defined F_FULLFSYNC) || \ + (defined __NetBSD__ && defined FDISKSYNC))) +# define PROVIDE_FULLFSYNC +# endif #if defined(__sgi)&&_COMPILER_VERSION>=700 /* declare ctermid_r if compiling with MIPSPro 7.x in ANSI C mode @@ -2129,6 +2134,41 @@ { return posix_fildes(fdobj, fsync); } + +# ifdef PROVIDE_FULLFSYNC +PyDoc_STRVAR(fullfsync__doc__, +"fullfsync(fd)\n\n" +"force write of file buffers to disk, and the flush of disk caches\n" +"of the file given by file descriptor fd."); + +static PyObject * +fullfsync(PyObject *self, PyObject *fdobj) +{ + /* See issue 11877 discussion */ + int res, fd = PyObject_AsFileDescriptor(fdobj); + if (fd < 0) + return NULL; + if (!_PyVerify_fd(fd)) + return posix_error(); + + Py_BEGIN_ALLOW_THREADS +# if defined __APPLE__ + /* F_FULLFSYNC is not supported for all types of FDs/FSYSs; + * be on the safe side and test for inappropriate ioctl errors */ + res = fcntl(fd, F_FULLFSYNC); + if (res < 0 && errno == ENOTTY) + res = fsync(fd); +# elif defined __NetBSD__ + res = fsync_range(fd, FFILESYNC | FDISKSYNC, 0, 0); +# endif + Py_END_ALLOW_THREADS + + if (res < 0) + return posix_error(); + Py_INCREF(Py_None); + return Py_None; +} +# endif /* PROVIDE_FULLFSYNC */ #endif /* HAVE_FSYNC */ #ifdef HAVE_SYNC @@ -9473,6 +9513,9 @@ #endif #ifdef HAVE_FSYNC {"fsync", posix_fsync, METH_O, posix_fsync__doc__}, +# ifdef PROVIDE_FULLFSYNC + {"fullfsync", fullfsync, METH_O, fullfsync__doc__}, +# endif #endif #ifdef HAVE_SYNC {"sync", posix_sync, METH_NOARGS, posix_sync__doc__},