Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fb05db2

Browse files
committed
file_truncate(): provide full "large file" support on Windows, by
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.
1 parent 15d529a commit fb05db2

4 files changed

Lines changed: 86 additions & 39 deletions

File tree

Doc/lib/libstdtypes.tex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,9 +1154,8 @@ \subsection{File Objects
11541154
\begin{methoddesc}[file]{truncate}{\optional{size}}
11551155
Truncate the file's size. If the optional \var{size} argument
11561156
present, the file is truncated to (at most) that size. The size
1157-
defaults to the current position. Availability of this function
1158-
depends on the operating system version (for example, not all
1159-
\UNIX{} versions support this operation).
1157+
defaults to the current position.
1158+
Availability: Windows, many \UNIX variants.
11601159
\end{methoddesc}
11611160

11621161
\begin{methoddesc}[file]{write}{str}

Lib/test/test_largefile.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,29 @@ def expect(got_this, expect_this):
128128
expect(f.read(1), 'a') # the 'a' that was written at the end of the file above
129129
f.close()
130130

131-
132-
# XXX add tests for truncate if it exists
133-
# XXX has truncate ever worked on Windows? specifically on WinNT I get:
134-
# "IOError: [Errno 13] Permission denied"
135-
##try:
136-
## newsize = size - 10
137-
## f.seek(newsize)
138-
## f.truncate()
139-
## expect(f.tell(), newsize)
140-
## newsize = newsize - 1
141-
## f.seek(0)
142-
## f.truncate(newsize)
143-
## expect(f.tell(), newsize)
144-
##except AttributeError:
145-
## pass
131+
if hasattr(f, 'truncate'):
132+
if test_support.verbose:
133+
print 'try truncate'
134+
f = open(name, 'r+b')
135+
f.seek(0, 2)
136+
expect(f.tell(), size+1)
137+
# Cut it back via seek + truncate with no argument.
138+
newsize = size - 10
139+
f.seek(newsize)
140+
f.truncate()
141+
expect(f.tell(), newsize)
142+
# Ensure that truncate(bigger than true size) doesn't grow the file.
143+
f.truncate(size)
144+
expect(f.tell(), newsize)
145+
# Ensure that truncate(smaller than true size) shrinks the file.
146+
newsize -= 1
147+
f.seek(0)
148+
f.truncate(newsize)
149+
expect(f.tell(), newsize)
150+
# cut it waaaaay back
151+
f.truncate(1)
152+
f.seek(0)
153+
expect(len(f.read()), 1)
154+
f.close()
146155

147156
os.unlink(name)

Misc/NEWS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ C API
7373
- Because Python's magic number scheme broke on January 1st, we decided
7474
to stop Python development. Thanks for all the fish!
7575

76-
- Some of us don't like fish, so we changed Python's magic number
76+
- Some of us don't like fish, so we changed Python's magic number
7777
scheme to a new one. See Python/import.c for details.
7878

7979
New platforms
@@ -84,6 +84,10 @@ Tests
8484

8585
Windows
8686

87+
- file.truncate([newsize]) now works on Windows for all newsize values.
88+
It used to fail if newsize didn't fit in 32 bits, reflecting a
89+
limitation of MS _chsize (which is no longer used).
90+
8791
- os.waitpid() is now implemented for Windows, and can be used to block
8892
until a specified process exits. This is similar to, but not exactly
8993
the same as, os.waitpid() on POSIX systems. If you're waiting for

Objects/fileobject.c

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
#ifdef MS_WIN32
1212
#define fileno _fileno
13-
/* can (almost fully) duplicate with _chsize, see file_truncate */
13+
/* can simulate truncate with Win32 API functions; see file_truncate */
1414
#define HAVE_FTRUNCATE
15+
#define WINDOWS_LEAN_AND_MEAN
16+
#include <windows.h>
1517
#endif
1618

1719
#ifdef macintosh
@@ -379,6 +381,9 @@ file_truncate(PyFileObject *f, PyObject *args)
379381
newsizeobj = NULL;
380382
if (!PyArg_ParseTuple(args, "|O:truncate", &newsizeobj))
381383
return NULL;
384+
385+
/* Set newsize to current postion if newsizeobj NULL, else to the
386+
specified value. */
382387
if (newsizeobj != NULL) {
383388
#if !defined(HAVE_LARGEFILE_SUPPORT)
384389
newsize = PyInt_AsLong(newsizeobj);
@@ -389,37 +394,67 @@ file_truncate(PyFileObject *f, PyObject *args)
389394
#endif
390395
if (PyErr_Occurred())
391396
return NULL;
392-
} else {
393-
/* Default to current position*/
397+
}
398+
else {
399+
/* Default to current position. */
394400
Py_BEGIN_ALLOW_THREADS
395401
errno = 0;
396402
newsize = _portable_ftell(f->f_fp);
397403
Py_END_ALLOW_THREADS
398-
if (newsize == -1) {
399-
PyErr_SetFromErrno(PyExc_IOError);
400-
clearerr(f->f_fp);
401-
return NULL;
402-
}
404+
if (newsize == -1)
405+
goto onioerror;
403406
}
407+
408+
/* Flush the file. */
404409
Py_BEGIN_ALLOW_THREADS
405410
errno = 0;
406411
ret = fflush(f->f_fp);
407412
Py_END_ALLOW_THREADS
408-
if (ret != 0) goto onioerror;
413+
if (ret != 0)
414+
goto onioerror;
409415

410416
#ifdef MS_WIN32
411-
/* can use _chsize; if, however, the newsize overflows 32-bits then
412-
_chsize is *not* adequate; in this case, an OverflowError is raised */
413-
if (newsize > LONG_MAX) {
414-
PyErr_SetString(PyExc_OverflowError,
415-
"the new size is too long for _chsize (it is limited to 32-bit values)");
416-
return NULL;
417-
} else {
418-
Py_BEGIN_ALLOW_THREADS
417+
/* MS _chsize doesn't work if newsize doesn't fit in 32 bits,
418+
so don't even try using it. truncate() should never grow the
419+
file, but MS SetEndOfFile will grow a file, so we need to
420+
compare the specified newsize to the actual size. Some
421+
optimization could be done here when newsizeobj is NULL. */
422+
{
423+
Py_off_t currentEOF; /* actual size */
424+
HANDLE hFile;
425+
int error;
426+
427+
/* First move to EOF, and set currentEOF to the size. */
419428
errno = 0;
420-
ret = _chsize(fileno(f->f_fp), (long)newsize);
421-
Py_END_ALLOW_THREADS
422-
if (ret != 0) goto onioerror;
429+
if (_portable_fseek(f->f_fp, 0, SEEK_END) != 0)
430+
goto onioerror;
431+
errno = 0;
432+
currentEOF = _portable_ftell(f->f_fp);
433+
if (currentEOF == -1)
434+
goto onioerror;
435+
436+
if (newsize > currentEOF)
437+
newsize = currentEOF; /* never grow the file */
438+
439+
/* Move to newsize, and truncate the file there. */
440+
if (newsize != currentEOF) {
441+
errno = 0;
442+
if (_portable_fseek(f->f_fp, newsize, SEEK_SET) != 0)
443+
goto onioerror;
444+
Py_BEGIN_ALLOW_THREADS
445+
errno = 0;
446+
hFile = (HANDLE)_get_osfhandle(fileno(f->f_fp));
447+
error = hFile == (HANDLE)-1;
448+
if (!error) {
449+
error = SetEndOfFile(hFile) == 0;
450+
if (error)
451+
errno = EACCES;
452+
}
453+
Py_END_ALLOW_THREADS
454+
if (error)
455+
goto onioerror;
456+
}
457+
423458
}
424459
#else
425460
Py_BEGIN_ALLOW_THREADS

0 commit comments

Comments
 (0)