Skip to main content

Modules/_io/fileio.c

Source:

cpython 3.14 @ ab2d84fe1023/Modules/_io/fileio.c

Modules/_io/fileio.c implements FileIO, the raw unbuffered layer at the bottom of the io stack. It wraps a file descriptor and calls POSIX read(2), write(2), and lseek(2) directly. BufferedReader/BufferedWriter sit on top and add buffering.

Map

LinesSymbolRole
1-100fileio structObject layout; fd, flags, mode string, closefd
101-300fileio_initopen(2) with mode-string-to-flag translation; O_CLOEXEC
301-450fileio_read, fileio_readallUnbuffered read; readall grows a buffer
451-550fileio_writeUnbuffered write; handles partial writes
551-650fileio_seek, fileio_telllseek(2) wrappers
651-900fileio_truncate, fileio_close, type wiringTruncate, close, PyFileIO_Type

Reading

Opening and O_CLOEXEC

fileio_init translates the mode string (r, rb, w+b, etc.) to O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, and O_TRUNC flags. On Linux and macOS it always adds O_CLOEXEC so the file descriptor is not inherited across exec().

// Modules/_io/fileio.c:101 fileio_init
static int
fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
{
...
flags = O_RDONLY;
if (writable) flags = O_WRONLY | O_CREAT;
if (readable && writable) flags = O_RDWR | O_CREAT;
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
self->fd = open(name, flags, 0666);
...
}

closefd ownership

The closefd parameter controls whether FileIO closes the file descriptor on close(). When closefd=False, the FileIO object is a non-owning view of an externally managed fd. This is used internally by socket.makefile() and by callers that want to wrap a pre-opened fd.

// Modules/_io/fileio.c:651 fileio_close_impl
static PyObject *
fileio_close_impl(fileio *self)
{
if (self->closefd) {
self->fd = -1;
return _PyObject_CallMethodIdNoArgs((PyObject *)self,
&PyId_flush);
}
Py_RETURN_NONE;
}

readall: growing buffer

fileio_readall allocates a buffer and keeps calling read(2), doubling the buffer when it fills. This handles files whose size is not known in advance (e.g., /proc files on Linux that report st_size = 0).

// Modules/_io/fileio.c:390 fileio_readall
static PyObject *
fileio_readall(fileio *self)
{
Py_ssize_t bufsize = SMALLCHUNK;
PyObject *result = PyBytes_FromStringAndSize(NULL, bufsize);
Py_ssize_t total = 0;
while (1) {
Py_ssize_t n = read(self->fd, buf + total, bufsize - total);
if (n == 0) break;
total += n;
if (total == bufsize) { /* double and realloc */ }
}
return _PyBytes_Resize(&result, total);
}

gopy notes

Not yet ported. The planned package path is module/_io/. The Go equivalent wraps an *os.File, delegating to its Read, Write, and Seek methods. The closefd=False pattern maps to not calling f.Close() in the wrapper's finalizer.