]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
file_truncate(): Backported 2.3 code so that file.truncate(n) works on
authorTim Peters <tim.peters@gmail.com>
Wed, 30 Apr 2003 19:24:59 +0000 (19:24 +0000)
committerTim Peters <tim.peters@gmail.com>
Wed, 30 Apr 2003 19:24:59 +0000 (19:24 +0000)
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.

Lib/test/test_largefile.py
Misc/NEWS
Objects/fileobject.c

index bf3b0ac181ed29caf5933a5f51ed5bb64ac3be0f..fd6af7a137d87d4c375498c6d131f403f7fc3223 100644 (file)
@@ -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)
index e1ec2511916e609b23778f50bbc6f22db45ea3d4..e8a165486a5160593f8a13cf29aa1775eba08660 100644 (file)
--- 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.
 
index b38dbb5c4d4d8e7a50580f19dfc2c87471af6cb1..e9f068289054b864bf8674cd7dbf16e9fad23b18 100644 (file)
@@ -8,10 +8,12 @@
 #include <sys/types.h>
 #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 <windows.h>
 #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;