Skip to content

Commit

Permalink
pythonGH-87804: Fix counter overflow in statvfs on macOS
Browse files Browse the repository at this point in the history
On macOS the statvfs interface returns block counts as
32-bit integers, and that results in bad reporting for
larger disks.

Therefore reimplement statvfs in terms of statfs, which
does use 64-bit integers for block counts.

Tested using a sparse filesystem image of 100TB.
  • Loading branch information
ronaldoussoren committed Nov 18, 2022
1 parent 4f5e1cb commit ee55d73
Showing 1 changed file with 101 additions and 0 deletions.
101 changes: 101 additions & 0 deletions Modules/posixmodule.c
Expand Up @@ -42,6 +42,12 @@
# define EX_OK EXIT_SUCCESS
#endif

#ifdef __APPLE__
/* Needed for the implementation of os.statvfs */
# include <sys/param.h>
# include <sys/mount.h>
#endif

/* On android API level 21, 'AT_EACCESS' is not declared although
* HAVE_FACCESSAT is defined. */
#ifdef __ANDROID__
Expand Down Expand Up @@ -11382,6 +11388,59 @@ os_WSTOPSIG_impl(PyObject *module, int status)
#endif
#include <sys/statvfs.h>

#ifdef __APPLE__
/* On macOS struct statvfs uses 32-bit integers for block counts,
* resulting in overflow when filesystems are larger tan 4TB. Therefore
* os.statvfs is implemented in terms of statfs(2).
*/

static PyObject*
_pystatvfs_fromstructstatfs(PyObject *module, struct statfs st) {
PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType;
PyObject *v = PyStructSequence_New((PyTypeObject *)StatVFSResultType);
if (v == NULL)
return NULL;

long flags = 0;
if (st.f_flags & MNT_RDONLY) {
flags |= ST_RDONLY;
}
if (st.f_flags & MNT_NOSUID) {
flags |= ST_NOSUID;
}

_Static_assert(sizeof(st.f_blocks) == sizeof(long long), "assuming large file");

PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long) st.f_iosize));
PyStructSequence_SET_ITEM(v, 1, PyLong_FromLong((long) st.f_bsize));
PyStructSequence_SET_ITEM(v, 2,
PyLong_FromLongLong((long long) st.f_blocks));
PyStructSequence_SET_ITEM(v, 3,
PyLong_FromLongLong((long long) st.f_bfree));
PyStructSequence_SET_ITEM(v, 4,
PyLong_FromLongLong((long long) st.f_bavail));
PyStructSequence_SET_ITEM(v, 5,
PyLong_FromLongLong((long long) st.f_files));
PyStructSequence_SET_ITEM(v, 6,
PyLong_FromLongLong((long long) st.f_ffree));
PyStructSequence_SET_ITEM(v, 7,
PyLong_FromLongLong((long long) st.f_ffree));
PyStructSequence_SET_ITEM(v, 8, PyLong_FromLong((long) flags));

PyStructSequence_SET_ITEM(v, 9, PyLong_FromLong((long) NAME_MAX));
PyStructSequence_SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid.val[0]));
if (PyErr_Occurred()) {
Py_DECREF(v);
return NULL;
}

return v;
}

#else



static PyObject*
_pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) {
PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType;
Expand Down Expand Up @@ -11433,6 +11492,8 @@ _pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) {
return v;
}

#endif


/*[clinic input]
os.fstatvfs
Expand All @@ -11450,6 +11511,22 @@ os_fstatvfs_impl(PyObject *module, int fd)
{
int result;
int async_err = 0;
#ifdef __APPLE__
struct statfs st;
/* On macOS os.fstatvfs is implemented using fstatfs(2) because
* the former uses 32-bit values for block counts.
*/
do {
Py_BEGIN_ALLOW_THREADS
result = fstatfs(fd, &st);
Py_END_ALLOW_THREADS
} while (result != 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
if (result != 0)
return (!async_err) ? posix_error() : NULL;

return _pystatvfs_fromstructstatfs(module, st);
#else
struct statvfs st;

do {
Expand All @@ -11462,6 +11539,7 @@ os_fstatvfs_impl(PyObject *module, int fd)
return (!async_err) ? posix_error() : NULL;

return _pystatvfs_fromstructstatvfs(module, st);
#endif
}
#endif /* defined(HAVE_FSTATVFS) && defined(HAVE_SYS_STATVFS_H) */

Expand All @@ -11485,6 +11563,28 @@ os_statvfs_impl(PyObject *module, path_t *path)
/*[clinic end generated code: output=87106dd1beb8556e input=3f5c35791c669bd9]*/
{
int result;

#ifdef __APPLE__
/* On macOS os.statvfs is implemented using statfs(2)/fstatfs(2) because
* the former uses 32-bit values for block counts.
*/
struct statfs st;

Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
result = fstatfs(path->fd, &st);
}
else
result = statfs(path->narrow, &st);
Py_END_ALLOW_THREADS

if (result) {
return path_error(path);
}

return _pystatvfs_fromstructstatfs(module, st);

#else
struct statvfs st;

Py_BEGIN_ALLOW_THREADS
Expand All @@ -11502,6 +11602,7 @@ os_statvfs_impl(PyObject *module, path_t *path)
}

return _pystatvfs_fromstructstatvfs(module, st);
#endif
}
#endif /* defined(HAVE_STATVFS) && defined(HAVE_SYS_STATVFS_H) */

Expand Down

0 comments on commit ee55d73

Please sign in to comment.