diff options
-rw-r--r-- | Doc/lib/libstdtypes.tex | 5 | ||||
-rw-r--r-- | Lib/test/test_largefile.py | 39 | ||||
-rw-r--r-- | Misc/NEWS | 6 | ||||
-rw-r--r-- | Objects/fileobject.c | 75 |
4 files changed, 86 insertions, 39 deletions
diff --git a/Doc/lib/libstdtypes.tex b/Doc/lib/libstdtypes.tex index 2ba87c4..27f1c52 100644 --- a/Doc/lib/libstdtypes.tex +++ b/Doc/lib/libstdtypes.tex @@ -1154,9 +1154,8 @@ Files have the following methods: \begin{methoddesc}[file]{truncate}{\optional{size}} Truncate the file's size. If the optional \var{size} argument present, the file is truncated to (at most) that size. The size - defaults to the current position. Availability of this function - depends on the operating system version (for example, not all - \UNIX{} versions support this operation). + defaults to the current position. + Availability: Windows, many \UNIX variants. \end{methoddesc} \begin{methoddesc}[file]{write}{str} diff --git a/Lib/test/test_largefile.py b/Lib/test/test_largefile.py index abfee39..bc24635 100644 --- a/Lib/test/test_largefile.py +++ b/Lib/test/test_largefile.py @@ -128,20 +128,29 @@ expect(os.lseek(f.fileno(), size, 0), size) expect(f.read(1), 'a') # the 'a' that was written at the end of the file above f.close() - -# XXX add tests for truncate if it exists -# XXX has truncate ever worked on Windows? specifically on WinNT I get: -# "IOError: [Errno 13] Permission denied" -##try: -## newsize = size - 10 -## f.seek(newsize) -## f.truncate() -## expect(f.tell(), newsize) -## newsize = newsize - 1 -## f.seek(0) -## f.truncate(newsize) -## expect(f.tell(), newsize) -##except AttributeError: -## pass +if hasattr(f, 'truncate'): + if test_support.verbose: + print 'try truncate' + f = open(name, 'r+b') + f.seek(0, 2) + expect(f.tell(), size+1) + # Cut it back via seek + truncate with no argument. + newsize = size - 10 + f.seek(newsize) + f.truncate() + expect(f.tell(), newsize) + # Ensure that truncate(bigger than true size) doesn't grow the file. + f.truncate(size) + expect(f.tell(), newsize) + # Ensure that truncate(smaller than true size) shrinks the file. + newsize -= 1 + f.seek(0) + f.truncate(newsize) + expect(f.tell(), newsize) + # cut it waaaaay back + f.truncate(1) + f.seek(0) + expect(len(f.read()), 1) + f.close() os.unlink(name) @@ -73,7 +73,7 @@ C API - Because Python's magic number scheme broke on January 1st, we decided to stop Python development. Thanks for all the fish! -- Some of us don't like fish, so we changed Python's magic number +- Some of us don't like fish, so we changed Python's magic number scheme to a new one. See Python/import.c for details. New platforms @@ -84,6 +84,10 @@ Tests Windows +- file.truncate([newsize]) now works on Windows for all newsize values. + It used to fail if newsize didn't fit in 32 bits, reflecting a + limitation of MS _chsize (which is no longer used). + - os.waitpid() is now implemented for Windows, and can be used to block until a specified process exits. This is similar to, but not exactly the same as, os.waitpid() on POSIX systems. If you're waiting for diff --git a/Objects/fileobject.c b/Objects/fileobject.c index d6b2229..f2f5dcf 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -10,8 +10,10 @@ #ifdef MS_WIN32 #define fileno _fileno -/* can (almost fully) duplicate with _chsize, see file_truncate */ +/* can simulate truncate with Win32 API functions; see file_truncate */ #define HAVE_FTRUNCATE +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> #endif #ifdef macintosh @@ -379,6 +381,9 @@ file_truncate(PyFileObject *f, PyObject *args) newsizeobj = NULL; if (!PyArg_ParseTuple(args, "|O:truncate", &newsizeobj)) return NULL; + + /* Set newsize to current postion if newsizeobj NULL, else to the + specified value. */ if (newsizeobj != NULL) { #if !defined(HAVE_LARGEFILE_SUPPORT) newsize = PyInt_AsLong(newsizeobj); @@ -389,37 +394,67 @@ file_truncate(PyFileObject *f, PyObject *args) #endif if (PyErr_Occurred()) return NULL; - } else { - /* Default to current position*/ + } + else { + /* Default to current position. */ Py_BEGIN_ALLOW_THREADS errno = 0; newsize = _portable_ftell(f->f_fp); Py_END_ALLOW_THREADS - if (newsize == -1) { - PyErr_SetFromErrno(PyExc_IOError); - clearerr(f->f_fp); - return NULL; - } + if (newsize == -1) + goto onioerror; } + + /* Flush the file. */ Py_BEGIN_ALLOW_THREADS errno = 0; ret = fflush(f->f_fp); Py_END_ALLOW_THREADS - if (ret != 0) goto onioerror; + if (ret != 0) + goto onioerror; #ifdef MS_WIN32 - /* can use _chsize; if, however, the newsize overflows 32-bits then - _chsize is *not* adequate; in this case, an OverflowError is raised */ - if (newsize > LONG_MAX) { - PyErr_SetString(PyExc_OverflowError, - "the new size is too long for _chsize (it is limited to 32-bit values)"); - return NULL; - } else { - Py_BEGIN_ALLOW_THREADS + /* MS _chsize doesn't work if newsize doesn't fit in 32 bits, + so don't even try using it. truncate() should never grow the + file, but MS SetEndOfFile will grow a file, so we need to + compare the specified newsize to the actual size. Some + optimization could be done here when newsizeobj is NULL. */ + { + Py_off_t currentEOF; /* actual size */ + HANDLE hFile; + int error; + + /* First move to EOF, and set currentEOF to the size. */ errno = 0; - ret = _chsize(fileno(f->f_fp), (long)newsize); - Py_END_ALLOW_THREADS - if (ret != 0) goto onioerror; + if (_portable_fseek(f->f_fp, 0, SEEK_END) != 0) + goto onioerror; + errno = 0; + currentEOF = _portable_ftell(f->f_fp); + if (currentEOF == -1) + goto onioerror; + + if (newsize > currentEOF) + newsize = currentEOF; /* never grow the file */ + + /* Move to newsize, and truncate the file there. */ + if (newsize != currentEOF) { + errno = 0; + if (_portable_fseek(f->f_fp, newsize, SEEK_SET) != 0) + goto onioerror; + Py_BEGIN_ALLOW_THREADS + errno = 0; + hFile = (HANDLE)_get_osfhandle(fileno(f->f_fp)); + error = hFile == (HANDLE)-1; + if (!error) { + error = SetEndOfFile(hFile) == 0; + if (error) + errno = EACCES; + } + Py_END_ALLOW_THREADS + if (error) + goto onioerror; + } + } #else Py_BEGIN_ALLOW_THREADS |