]> granicus.if.org Git - python/commitdiff
file_truncate(): provide full "large file" support on Windows, by
authorTim Peters <tim.peters@gmail.com>
Mon, 11 Mar 2002 00:24:00 +0000 (00:24 +0000)
committerTim Peters <tim.peters@gmail.com>
Mon, 11 Mar 2002 00:24:00 +0000 (00:24 +0000)
dropping MS's inadequate _chsize() function.  This was inspired by
SF patch 498109 ("fileobject truncate support for win32"), which I
rejected.

libstdtypes.tex:  Someone who knows should update the availability
blurb.  For example, if it's available on Linux, it would be good to
say so.

test_largefile:  Uncommented the file.truncate() tests, and reworked to
do more.  The old comment about "permission errors" in the truncation
tests under Windows was almost certainly due to that the file wasn't open
for *write* access at this point, so of course MS wouldn't let you
truncate it.  I'd be appalled if a Unixish system did.

CAUTION:  Someone should run this test on Linux (etc) too.  The
truncation part was commented out before.  Note that test_largefile isn't
run by default.

Doc/lib/libstdtypes.tex
Lib/test/test_largefile.py
Misc/NEWS
Objects/fileobject.c

index 2ba87c4a9fed5c17bb163256e434324791695bae..27f1c52f0f89227c2e408d9bd03cbfaead4d3d10 100644 (file)
@@ -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}
index abfee39e988a570e6e0833486aca48ddeb9f0eda..bc246353b5a360f491e5512a70b03c6e1bf89fa0 100644 (file)
@@ -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)
index 58d5a66e21d94b307a45d963362fe4cdadd647e4..6611c5dcd09e5da2b8ed6fd3989fa1df468aff2e 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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
index d6b22297ff4da68e831be0ada187bf57c883d282..f2f5dcf595625e9eac8314386f5158c677e85230 100644 (file)
 
 #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