Skip to content

Commit 3ad0cf0

Browse files
gh-137044: Support large limit values in getrlimit() and setrlimit()
* Return large limit values as positive integers instead of negative integers in resource.getrlimit(). * Accept large values and reject negative values (except RLIM_INFINITY) for limits in resource.setrlimit().
1 parent 158b28d commit 3ad0cf0

File tree

3 files changed

+172
-114
lines changed

3 files changed

+172
-114
lines changed

Lib/test/test_resource.py

Lines changed: 120 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,89 +14,133 @@ class ResourceTest(unittest.TestCase):
1414

1515
def test_args(self):
1616
self.assertRaises(TypeError, resource.getrlimit)
17-
self.assertRaises(TypeError, resource.getrlimit, 42, 42)
17+
self.assertRaises(TypeError, resource.getrlimit, 0, 42)
18+
self.assertRaises(OverflowError, resource.getrlimit, 2**1000)
19+
self.assertRaises(OverflowError, resource.getrlimit, -2**1000)
20+
self.assertRaises(TypeError, resource.getrlimit, '0')
1821
self.assertRaises(TypeError, resource.setrlimit)
19-
self.assertRaises(TypeError, resource.setrlimit, 42, 42, 42)
22+
self.assertRaises(TypeError, resource.setrlimit, 0)
23+
self.assertRaises(TypeError, resource.setrlimit, 0, 42)
24+
self.assertRaises(TypeError, resource.setrlimit, 0, 42, 42)
25+
self.assertRaises(OverflowError, resource.setrlimit, 2**1000, (42, 42))
26+
self.assertRaises(OverflowError, resource.setrlimit, -2**1000, (42, 42))
27+
self.assertRaises(ValueError, resource.setrlimit, 0, (42,))
28+
self.assertRaises(ValueError, resource.setrlimit, 0, (42, 42, 42))
29+
self.assertRaises(TypeError, resource.setrlimit, '0', (42, 42))
30+
self.assertRaises(TypeError, resource.setrlimit, 0, ('42', 42))
31+
self.assertRaises(TypeError, resource.setrlimit, 0, (42, '42'))
2032

2133
@unittest.skipIf(sys.platform == "vxworks",
2234
"setting RLIMIT_FSIZE is not supported on VxWorks")
35+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
2336
def test_fsize_ismax(self):
24-
try:
25-
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
26-
except AttributeError:
27-
pass
28-
else:
29-
# RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really big
30-
# number on a platform with large file support. On these platforms,
31-
# we need to test that the get/setrlimit functions properly convert
32-
# the number to a C long long and that the conversion doesn't raise
33-
# an error.
34-
self.assertEqual(resource.RLIM_INFINITY, max)
35-
resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
37+
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
38+
# RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really big
39+
# number on a platform with large file support. On these platforms,
40+
# we need to test that the get/setrlimit functions properly convert
41+
# the number to a C long long and that the conversion doesn't raise
42+
# an error.
43+
self.assertEqual(resource.RLIM_INFINITY, max)
44+
resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
3645

46+
@unittest.skipIf(sys.platform == "vxworks",
47+
"setting RLIMIT_FSIZE is not supported on VxWorks")
48+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
3749
def test_fsize_enforced(self):
50+
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
51+
# Check to see what happens when the RLIMIT_FSIZE is small. Some
52+
# versions of Python were terminated by an uncaught SIGXFSZ, but
53+
# pythonrun.c has been fixed to ignore that exception. If so, the
54+
# write() should return EFBIG when the limit is exceeded.
55+
56+
# At least one platform has an unlimited RLIMIT_FSIZE and attempts
57+
# to change it raise ValueError instead.
3858
try:
39-
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
40-
except AttributeError:
41-
pass
42-
else:
43-
# Check to see what happens when the RLIMIT_FSIZE is small. Some
44-
# versions of Python were terminated by an uncaught SIGXFSZ, but
45-
# pythonrun.c has been fixed to ignore that exception. If so, the
46-
# write() should return EFBIG when the limit is exceeded.
47-
48-
# At least one platform has an unlimited RLIMIT_FSIZE and attempts
49-
# to change it raise ValueError instead.
5059
try:
60+
resource.setrlimit(resource.RLIMIT_FSIZE, (1024, max))
61+
limit_set = True
62+
except ValueError:
63+
limit_set = False
64+
f = open(os_helper.TESTFN, "wb")
65+
try:
66+
f.write(b"X" * 1024)
5167
try:
52-
resource.setrlimit(resource.RLIMIT_FSIZE, (1024, max))
53-
limit_set = True
54-
except ValueError:
55-
limit_set = False
56-
f = open(os_helper.TESTFN, "wb")
57-
try:
58-
f.write(b"X" * 1024)
59-
try:
60-
f.write(b"Y")
68+
f.write(b"Y")
69+
f.flush()
70+
# On some systems (e.g., Ubuntu on hppa) the flush()
71+
# doesn't always cause the exception, but the close()
72+
# does eventually. Try flushing several times in
73+
# an attempt to ensure the file is really synced and
74+
# the exception raised.
75+
for i in range(5):
76+
time.sleep(.1)
6177
f.flush()
62-
# On some systems (e.g., Ubuntu on hppa) the flush()
63-
# doesn't always cause the exception, but the close()
64-
# does eventually. Try flushing several times in
65-
# an attempt to ensure the file is really synced and
66-
# the exception raised.
67-
for i in range(5):
68-
time.sleep(.1)
69-
f.flush()
70-
except OSError:
71-
if not limit_set:
72-
raise
73-
if limit_set:
74-
# Close will attempt to flush the byte we wrote
75-
# Restore limit first to avoid getting a spurious error
76-
resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
77-
finally:
78-
f.close()
79-
finally:
78+
except OSError:
79+
if not limit_set:
80+
raise
8081
if limit_set:
82+
# Close will attempt to flush the byte we wrote
83+
# Restore limit first to avoid getting a spurious error
8184
resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
82-
os_helper.unlink(os_helper.TESTFN)
85+
finally:
86+
f.close()
87+
finally:
88+
if limit_set:
89+
resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
90+
os_helper.unlink(os_helper.TESTFN)
8391

84-
def test_fsize_toobig(self):
92+
@unittest.skipIf(sys.platform == "vxworks",
93+
"setting RLIMIT_FSIZE is not supported on VxWorks")
94+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
95+
def test_fsize_too_big(self):
8596
# Be sure that setrlimit is checking for really large values
8697
too_big = 10**50
98+
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
99+
try:
100+
resource.setrlimit(resource.RLIMIT_FSIZE, (too_big, max))
101+
except (OverflowError, ValueError):
102+
pass
87103
try:
88-
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
89-
except AttributeError:
104+
resource.setrlimit(resource.RLIMIT_FSIZE, (max, too_big))
105+
except (OverflowError, ValueError):
106+
pass
107+
108+
@unittest.skipIf(sys.platform == "vxworks",
109+
"setting RLIMIT_FSIZE is not supported on VxWorks")
110+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
111+
def test_fsize_not_too_big(self):
112+
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
113+
self.addCleanup(resource.setrlimit, resource.RLIMIT_FSIZE, (cur, max))
114+
115+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**31-1, max))
116+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**31-1, max))
117+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**31, max))
118+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**31, max))
119+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**32-2, max))
120+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**32-2, max))
121+
122+
try:
123+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**32, max))
124+
except OverflowError:
90125
pass
91126
else:
92-
try:
93-
resource.setrlimit(resource.RLIMIT_FSIZE, (too_big, max))
94-
except (OverflowError, ValueError):
95-
pass
96-
try:
97-
resource.setrlimit(resource.RLIMIT_FSIZE, (max, too_big))
98-
except (OverflowError, ValueError):
99-
pass
127+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**32, max))
128+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**63-1, max))
129+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**63-1, max))
130+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**63, max))
131+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**63, max))
132+
resource.setrlimit(resource.RLIMIT_FSIZE, (2**64-2, max))
133+
self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**64-2, max))
134+
135+
@unittest.skipIf(sys.platform == "vxworks",
136+
"setting RLIMIT_FSIZE is not supported on VxWorks")
137+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
138+
def test_fsize_negative(self):
139+
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
140+
for value in -2, -2**31, -2**32-1, -2**63, -2**64-1, -2**1000:
141+
with self.subTest(value=value):
142+
self.assertRaises(ValueError, resource.setrlimit, resource.RLIMIT_FSIZE, (value, max))
143+
self.assertRaises(ValueError, resource.setrlimit, resource.RLIMIT_FSIZE, (cur, value))
100144

101145
@unittest.skipUnless(hasattr(resource, "getrusage"), "needs getrusage")
102146
def test_getrusage(self):
@@ -117,21 +161,18 @@ def test_getrusage(self):
117161
# Issue 6083: Reference counting bug
118162
@unittest.skipIf(sys.platform == "vxworks",
119163
"setting RLIMIT_CPU is not supported on VxWorks")
164+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_CPU'), 'requires resource.RLIMIT_CPU')
120165
def test_setrusage_refcount(self):
121-
try:
122-
limits = resource.getrlimit(resource.RLIMIT_CPU)
123-
except AttributeError:
124-
pass
125-
else:
126-
class BadSequence:
127-
def __len__(self):
128-
return 2
129-
def __getitem__(self, key):
130-
if key in (0, 1):
131-
return len(tuple(range(1000000)))
132-
raise IndexError
166+
limits = resource.getrlimit(resource.RLIMIT_CPU)
167+
class BadSequence:
168+
def __len__(self):
169+
return 2
170+
def __getitem__(self, key):
171+
if key in (0, 1):
172+
return len(tuple(range(1000000)))
173+
raise IndexError
133174

134-
resource.setrlimit(resource.RLIMIT_CPU, BadSequence())
175+
resource.setrlimit(resource.RLIMIT_CPU, BadSequence())
135176

136177
def test_pagesize(self):
137178
pagesize = resource.getpagesize()
@@ -168,7 +209,8 @@ class BadSeq:
168209
def __len__(self):
169210
return 2
170211
def __getitem__(self, key):
171-
return limits[key] - 1 # new reference
212+
lim = limits[key]
213+
return lim - 1 if lim > 0 else lim + 0xFFFF_FFFF # new reference
172214

173215
limits = resource.getrlimit(resource.RLIMIT_AS)
174216
self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS, BadSeq()),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Return large limit values as positive integers instead of negative integers
2+
in :func:`resource.getrlimit`. Accept large values and reject negative
3+
values (except :data:`~resource.RLIM_INFINITY`) for limits in
4+
:func:`resource.setrlimit`.

Modules/resource.c

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
// Need limited C API version 3.13 for PySys_Audit()
2-
#include "pyconfig.h" // Py_GIL_DISABLED
3-
#ifndef Py_GIL_DISABLED
4-
# define Py_LIMITED_API 0x030d0000
5-
#endif
6-
71
#include "Python.h"
82
#include <errno.h> // errno
93
#include <string.h>
@@ -150,6 +144,35 @@ resource_getrusage_impl(PyObject *module, int who)
150144
}
151145
#endif
152146

147+
static int
148+
py2rlim(PyObject *obj, rlim_t *out)
149+
{
150+
obj = PyNumber_Index(obj);
151+
if (obj == NULL) {
152+
return -1;
153+
}
154+
int neg = PyLong_IsNegative(obj);
155+
assert(neg >= 0);
156+
Py_ssize_t bytes = PyLong_AsNativeBytes(obj, out, sizeof(*out),
157+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
158+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
159+
Py_DECREF(obj);
160+
if (bytes < 0) {
161+
return -1;
162+
}
163+
else if (neg && (*out != RLIM_INFINITY || bytes > (Py_ssize_t)sizeof(*out))) {
164+
PyErr_SetString(PyExc_ValueError,
165+
"Cannot convert negative int");
166+
return -1;
167+
}
168+
else if (bytes > (Py_ssize_t)sizeof(*out)) {
169+
PyErr_SetString(PyExc_OverflowError,
170+
"Python int too large to convert to C rlim_t");
171+
return -1;
172+
}
173+
return 0;
174+
}
175+
153176
static int
154177
py2rlimit(PyObject *limits, struct rlimit *rl_out)
155178
{
@@ -166,42 +189,38 @@ py2rlimit(PyObject *limits, struct rlimit *rl_out)
166189
}
167190
curobj = PyTuple_GetItem(limits, 0); // borrowed
168191
maxobj = PyTuple_GetItem(limits, 1); // borrowed
169-
#if !defined(HAVE_LARGEFILE_SUPPORT)
170-
rl_out->rlim_cur = PyLong_AsLong(curobj);
171-
if (rl_out->rlim_cur == (rlim_t)-1 && PyErr_Occurred())
172-
goto error;
173-
rl_out->rlim_max = PyLong_AsLong(maxobj);
174-
if (rl_out->rlim_max == (rlim_t)-1 && PyErr_Occurred())
175-
goto error;
176-
#else
177-
/* The limits are probably bigger than a long */
178-
rl_out->rlim_cur = PyLong_AsLongLong(curobj);
179-
if (rl_out->rlim_cur == (rlim_t)-1 && PyErr_Occurred())
180-
goto error;
181-
rl_out->rlim_max = PyLong_AsLongLong(maxobj);
182-
if (rl_out->rlim_max == (rlim_t)-1 && PyErr_Occurred())
192+
if (py2rlim(curobj, &rl_out->rlim_cur) < 0 ||
193+
py2rlim(maxobj, &rl_out->rlim_max) < 0)
194+
{
183195
goto error;
184-
#endif
196+
}
185197

186198
Py_DECREF(limits);
187-
rl_out->rlim_cur = rl_out->rlim_cur & RLIM_INFINITY;
188-
rl_out->rlim_max = rl_out->rlim_max & RLIM_INFINITY;
189199
return 0;
190200

191201
error:
192202
Py_DECREF(limits);
193203
return -1;
194204
}
195205

206+
static PyObject*
207+
rlim2py(rlim_t value)
208+
{
209+
if (value == RLIM_INFINITY) {
210+
return PyLong_FromNativeBytes(&value, sizeof(value), -1);
211+
}
212+
return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1);
213+
}
214+
196215
static PyObject*
197216
rlimit2py(struct rlimit rl)
198217
{
199-
if (sizeof(rl.rlim_cur) > sizeof(long)) {
200-
return Py_BuildValue("LL",
201-
(long long) rl.rlim_cur,
202-
(long long) rl.rlim_max);
218+
PyObject *cur = rlim2py(rl.rlim_cur);
219+
if (cur == NULL) {
220+
return NULL;
203221
}
204-
return Py_BuildValue("ll", (long) rl.rlim_cur, (long) rl.rlim_max);
222+
PyObject *max = rlim2py(rl.rlim_max);
223+
return Py_BuildValue("NN", cur, max);
205224
}
206225

207226
/*[clinic input]
@@ -495,14 +514,7 @@ resource_exec(PyObject *module)
495514
ADD_INT(module, RLIMIT_KQUEUES);
496515
#endif
497516

498-
PyObject *v;
499-
if (sizeof(RLIM_INFINITY) > sizeof(long)) {
500-
v = PyLong_FromLongLong((long long) RLIM_INFINITY);
501-
} else
502-
{
503-
v = PyLong_FromLong((long) RLIM_INFINITY);
504-
}
505-
if (PyModule_Add(module, "RLIM_INFINITY", v) < 0) {
517+
if (PyModule_Add(module, "RLIM_INFINITY", rlim2py(RLIM_INFINITY)) < 0) {
506518
return -1;
507519
}
508520
return 0;

0 commit comments

Comments
 (0)