summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/libstdtypes.tex5
-rw-r--r--Lib/test/test_largefile.py39
-rw-r--r--Misc/NEWS6
-rw-r--r--Objects/fileobject.c75
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)
diff --git a/Misc/NEWS b/Misc/NEWS
index 58d5a66..6611c5d 100644
--- 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
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