From: Tim Peters Date: Wed, 30 Apr 2003 19:24:59 +0000 (+0000) Subject: file_truncate(): Backported 2.3 code so that file.truncate(n) works on X-Git-Tag: v2.2.3c1~47 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fa90c3e468d2b112745f984a9881f944f407f18f;p=thirdparty%2FPython%2Fcpython.git file_truncate(): Backported 2.3 code so that file.truncate(n) works on Windows when n is too big to fit in a 32-bit int. This was a hole in 2.2's large file support on Windows, and turns out it's a bad hole at least for ZODB. --- diff --git a/Lib/test/test_largefile.py b/Lib/test/test_largefile.py index bf3b0ac181ed..fd6af7a137d8 100644 --- a/Lib/test/test_largefile.py +++ b/Lib/test/test_largefile.py @@ -128,20 +128,35 @@ 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) # else we've lost track of the true size + # Cut it back via seek + truncate with no argument. + newsize = size - 10 + f.seek(newsize) + f.truncate() + expect(f.tell(), newsize) # else pointer moved + f.seek(0, 2) + expect(f.tell(), newsize) # else wasn't truncated + # Ensure that truncate(smaller than true size) shrinks the file. + newsize -= 1 + f.seek(42) + f.truncate(newsize) + expect(f.tell(), 42) # else pointer moved + f.seek(0, 2) + expect(f.tell(), newsize) # else wasn't truncated + + # XXX truncate(larger than true size) is ill-defined across platforms + + # cut it waaaaay back + f.seek(0) + f.truncate(1) + expect(f.tell(), 0) # else pointer moved + expect(len(f.read()), 1) # else wasn't truncated + + f.close() os.unlink(name) diff --git a/Misc/NEWS b/Misc/NEWS index e1ec2511916e..e8a165486a51 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -2,6 +2,10 @@ What's New in Python 2.2.3 ? Release date: XX-XXX-2003 ============================ +- Windows: file.truncate(size) failed on large files when size didn't + fit in 32 bits. This was fixed by backporting new Windows truncation + code and tests from 2.3. + - It is no longer possible to use object.__setattr__ to circumvent the restrictions on setting attributes of type objects. diff --git a/Objects/fileobject.c b/Objects/fileobject.c index b38dbb5c4d4d..e9f068289054 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -8,10 +8,12 @@ #include #endif /* DONT_HAVE_SYS_TYPES_H */ -#ifdef MS_WIN32 +#ifdef MS_WINDOWS #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 WIN32_LEAN_AND_MEAN +#include #endif #ifdef macintosh @@ -388,6 +390,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); @@ -398,37 +403,80 @@ file_truncate(PyFileObject *f, PyObject *args) #endif if (PyErr_Occurred()) return NULL; - } else { + } + 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 { +#ifdef MS_WINDOWS + /* MS _chsize doesn't work if newsize doesn't fit in 32 bits, + so don't even try using it. */ + { + Py_off_t current; /* current file position */ + HANDLE hFile; + int error; + + /* current <- current file postion. */ + if (newsizeobj == NULL) + current = newsize; + else { + Py_BEGIN_ALLOW_THREADS + errno = 0; + current = _portable_ftell(f->f_fp); + Py_END_ALLOW_THREADS + if (current == -1) + goto onioerror; + } + + /* Move to newsize. */ + if (current != newsize) { + Py_BEGIN_ALLOW_THREADS + errno = 0; + error = _portable_fseek(f->f_fp, newsize, SEEK_SET) + != 0; + Py_END_ALLOW_THREADS + if (error) + goto onioerror; + } + + /* Truncate. Note that this may grow the file! */ Py_BEGIN_ALLOW_THREADS errno = 0; - ret = _chsize(fileno(f->f_fp), (long)newsize); + 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 (ret != 0) goto onioerror; + if (error) + goto onioerror; + + /* Restore original file position. */ + if (current != newsize) { + Py_BEGIN_ALLOW_THREADS + errno = 0; + error = _portable_fseek(f->f_fp, current, SEEK_SET) + != 0; + Py_END_ALLOW_THREADS + if (error) + goto onioerror; + } } #else Py_BEGIN_ALLOW_THREADS @@ -436,7 +484,7 @@ file_truncate(PyFileObject *f, PyObject *args) ret = ftruncate(fileno(f->f_fp), newsize); Py_END_ALLOW_THREADS if (ret != 0) goto onioerror; -#endif /* !MS_WIN32 */ +#endif /* !MS_WINDOWS */ Py_INCREF(Py_None); return Py_None;