Skip to content

Commit a5ebc20

Browse files
matrixisevstinner
authored andcommitted
[3.6] bpo-24658: Fix read/write greater than 2 GiB on macOS (GH-1705) (GH-9937)
On macOS, fix reading from and writing into a file with a size larger than 2 GiB. (cherry picked from commit 74a8b6e)
1 parent 669fa8b commit a5ebc20

File tree

5 files changed

+34
-27
lines changed

5 files changed

+34
-27
lines changed

Include/fileutils.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ PyAPI_FUNC(char*) _Py_EncodeLocaleEx(
2929

3030
PyAPI_FUNC(PyObject *) _Py_device_encoding(int);
3131

32+
#if defined(MS_WINDOWS) || defined(__APPLE__)
33+
/* On Windows, the count parameter of read() is an int (bpo-9015, bpo-9611).
34+
On macOS 10.13, read() and write() with more than INT_MAX bytes
35+
fail with EINVAL (bpo-24658). */
36+
# define _PY_READ_MAX INT_MAX
37+
# define _PY_WRITE_MAX INT_MAX
38+
#else
39+
/* write() should truncate the input to PY_SSIZE_T_MAX bytes,
40+
but it's safer to do it ourself to have a portable behaviour */
41+
# define _PY_READ_MAX PY_SSIZE_T_MAX
42+
# define _PY_WRITE_MAX PY_SSIZE_T_MAX
43+
#endif
44+
3245
#ifdef MS_WINDOWS
3346
struct _Py_stat_struct {
3447
unsigned long st_dev;

Lib/test/test_largefile.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import stat
66
import sys
77
import unittest
8-
from test.support import TESTFN, requires, unlink
8+
from test.support import TESTFN, requires, unlink, bigmemtest
99
import io # C implementation of io
1010
import _pyio as pyio # Python implementation of io
1111

12-
# size of file to create (>2GB; 2GB == 2147483648 bytes)
13-
size = 2500000000
12+
# size of file to create (>2 GiB; 2 GiB == 2,147,483,648 bytes)
13+
size = 2_500_000_000
1414

1515
class LargeFileTest:
1616
"""Test that each file function works as expected for large
@@ -45,6 +45,15 @@ def tearDownClass(cls):
4545
raise cls.failureException('File was not truncated by opening '
4646
'with mode "wb"')
4747

48+
# _pyio.FileIO.readall() uses a temporary bytearray then casted to bytes,
49+
# so memuse=2 is needed
50+
@bigmemtest(size=size, memuse=2, dry_run=False)
51+
def test_large_read(self, _size):
52+
# bpo-24658: Test that a read greater than 2GB does not fail.
53+
with self.open(TESTFN, "rb") as f:
54+
self.assertEqual(len(f.read()), size + 1)
55+
self.assertEqual(f.tell(), size + 1)
56+
4857
def test_osstat(self):
4958
self.assertEqual(os.stat(TESTFN)[stat.ST_SIZE], size+1)
5059

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
On macOS, fix reading from and writing into a file with a size larger than 2 GiB.

Modules/_io/fileio.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -799,11 +799,9 @@ _io_FileIO_read_impl(fileio *self, Py_ssize_t size)
799799
if (size < 0)
800800
return _io_FileIO_readall_impl(self);
801801

802-
#ifdef MS_WINDOWS
803-
/* On Windows, the count parameter of read() is an int */
804-
if (size > INT_MAX)
805-
size = INT_MAX;
806-
#endif
802+
if (size > _PY_READ_MAX) {
803+
size = _PY_READ_MAX;
804+
}
807805

808806
bytes = PyBytes_FromStringAndSize(NULL, size);
809807
if (bytes == NULL)

Python/fileutils.c

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,18 +1263,9 @@ _Py_read(int fd, void *buf, size_t count)
12631263
* handler raised an exception. */
12641264
assert(!PyErr_Occurred());
12651265

1266-
#ifdef MS_WINDOWS
1267-
if (count > INT_MAX) {
1268-
/* On Windows, the count parameter of read() is an int */
1269-
count = INT_MAX;
1270-
}
1271-
#else
1272-
if (count > PY_SSIZE_T_MAX) {
1273-
/* if count is greater than PY_SSIZE_T_MAX,
1274-
* read() result is undefined */
1275-
count = PY_SSIZE_T_MAX;
1266+
if (count > _PY_READ_MAX) {
1267+
count = _PY_READ_MAX;
12761268
}
1277-
#endif
12781269

12791270
_Py_BEGIN_SUPPRESS_IPH
12801271
do {
@@ -1325,15 +1316,10 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
13251316
depending on heap usage). */
13261317
count = 32767;
13271318
}
1328-
else if (count > INT_MAX)
1329-
count = INT_MAX;
1330-
#else
1331-
if (count > PY_SSIZE_T_MAX) {
1332-
/* write() should truncate count to PY_SSIZE_T_MAX, but it's safer
1333-
* to do it ourself to have a portable behaviour. */
1334-
count = PY_SSIZE_T_MAX;
1335-
}
13361319
#endif
1320+
if (count > _PY_WRITE_MAX) {
1321+
count = _PY_WRITE_MAX;
1322+
}
13371323

13381324
if (gil_held) {
13391325
do {

0 commit comments

Comments
 (0)