Skip to main content

Modules/posixmodule.c

Source:

cpython 3.14 @ ab2d84fe1023/Modules/posixmodule.c

posixmodule.c is the largest C file in CPython. It implements the os module on POSIX platforms and the nt module on Windows. A top-of-file #ifdef MS_WINDOWS block selects the module name; the rest of the file is shared between both platforms with per-platform sections guarded by further #ifdef blocks. The pure-Python os.py in Lib/ imports this extension and re-exports everything, adding path-manipulation helpers and os.path delegation.

Map

SymbolKindLines (approx)Purpose
#ifdef MS_WINDOWS name selectionpreprocessor1-80Sets MODNAME to "nt" or "posix"
stat_result / StatResultstruct + type400-600Holds os.stat() return values
os_stat_implfunction2200-2320os.stat(path, ...) implementation
os_lstat_implfunction2325-2380os.lstat variant (no symlink follow)
os_open_implfunction3100-3200os.open(path, flags, mode)
os_read_implfunction3250-3310os.read(fd, n)
os_write_implfunction3315-3380os.write(fd, data)
os_close_implfunction3400-3430os.close(fd)
DirEntrystruct + type8500-8700Single entry from os.scandir() iterator
ScandirIteratorstruct + type8750-8900State for the os.scandir() iterator
os_scandir_implfunction8950-9100Opens directory and returns ScandirIterator
ScandirIterator_iternextfunction9105-9250Yields DirEntry objects one at a time
DirEntry_statmethod9300-9400Cached stat() on a DirEntry
os_getcwd_implfunction5100-5160os.getcwd()
os_chdir_implfunction5200-5240os.chdir(path)
os_listdir_implfunction8200-8350os.listdir() (non-iterator, returns list)
path_converterfunction700-900Converts path arg to fd or char* or wchar_t*
posix_module_execfunction15800-16000Module init: registers types, constants, os.environ

Reading

os.stat: stat_result struct and os_stat_impl

stat_result is a Python structseq type. structseq is CPython's fixed-length tuple subclass, used here so that st_mode, st_ino, etc. are accessible both by name and by index (for backward compatibility with os.stat()[0]).

// CPython: Modules/posixmodule.c:400 stat_result_desc
static PyStructSequence_Field stat_result_fields[] = {
{"st_mode", "protection bits"},
{"st_ino", "inode"},
{"st_dev", "device"},
{"st_nlink", "number of hard links"},
{"st_uid", "user ID of file owner"},
{"st_gid", "group ID of file owner"},
{"st_size", "total size, in bytes"},
...
{0}
};

os_stat_impl is generated by the Argument Clinic tool (Modules/clinic/posixmodule.c.h) and then calls os_do_stat, a shared helper that invokes fstatat(2) on POSIX or GetFileInformationByHandleEx on Windows. The result is converted to a stat_result via _Py_set_53bit_double for the timestamp fields, which preserves nanosecond resolution as a Python float without losing the integer second.

// CPython: Modules/posixmodule.c:2280 os_stat_impl
static PyObject *
os_stat_impl(PyObject *module, path_t *path, int dir_fd, int follow_symlinks)
{
return posix_do_stat(module, "stat", path, follow_symlinks,
dir_fd, NULL);
}

os.open, os.read, os.write: POSIX wrappers

os_open_impl calls open(2) after translating the Python flags integer and mode integer directly. On Windows it calls _wopen with the same signature. The file descriptor returned is a plain C int; CPython wraps it in a Python int object but adds no fd-tracking abstraction at the C level.

// CPython: Modules/posixmodule.c:3120 os_open_impl
static int
os_open_impl(PyObject *module, path_t *path, int flags, int mode, int dir_fd)
{
int fd;
...
Py_BEGIN_ALLOW_THREADS
fd = OPEN(path->narrow, flags, mode);
Py_END_ALLOW_THREADS
if (fd < 0)
return posix_error();
return fd;
}

Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS release the GIL around blocking syscalls. Both os_read_impl and os_write_impl follow the same pattern, calling read(2) / write(2) inside those macros.

// CPython: Modules/posixmodule.c:3260 os_read_impl
static PyObject *
os_read_impl(PyObject *module, int fd, Py_ssize_t length)
{
Py_buffer buf;
...
Py_BEGIN_ALLOW_THREADS
n = read(fd, buf.buf, length);
Py_END_ALLOW_THREADS
...
}

os_write_impl accepts a Py_buffer (any bytes-like object) and returns the number of bytes written as a Python int, matching POSIX write(2) partial-write semantics. It does not retry on EINTR; that is handled by a higher-level os.write wrapper in Lib/os.py.

DirEntry and os.scandir iterator

os.scandir() avoids the cost of calling stat(2) once per entry on most platforms. On Linux it uses getdents64(2) (via opendir/readdir); on Windows it uses FindFirstFileW/FindNextFileW. Both paths populate a DirEntry object with the file type and inode already known from the directory listing, so DirEntry.is_dir() and DirEntry.is_file() are typically free.

// CPython: Modules/posixmodule.c:8520 DirEntry struct
typedef struct {
PyObject_HEAD
PyObject *name;
PyObject *path;
PyObject *stat;
PyObject *lstat;
#ifdef MS_WINDOWS
struct _Py_stat_struct win32_lstat;
uint64_t win32_file_index;
...
#else
ino_t d_ino;
unsigned char d_type;
#endif
} DirEntry;

ScandirIterator_iternext calls readdir(3) in a loop, skipping . and .., and constructs a new DirEntry for each result. The DirEntry.stat() method is lazy: it stores the result in DirEntry.stat (or DirEntry.lstat) on first access and returns the cached value on subsequent calls.

// CPython: Modules/posixmodule.c:9105 ScandirIterator_iternext
static PyObject *
ScandirIterator_iternext(ScandirIterator *iterator)
{
...
while (1) {
errno = 0;
direntp = readdir(iterator->dirp);
if (direntp == NULL) break;
if (direntp->d_name[0] == '.' &&
(direntp->d_name[1] == '\0' ||
(direntp->d_name[1] == '.' && direntp->d_name[2] == '\0')))
continue;
return DirEntry_from_posix_info(iterator->module, iterator, direntp);
}
...
}

os.path delegation and the WIN32/POSIX module name split

At the top of the file, #ifdef MS_WINDOWS controls two things: the C-level module name string and the selection of ntpath vs posixpath for os.path.

// CPython: Modules/posixmodule.c:60 module name selection
#ifdef MS_WINDOWS
# define MODNAME "nt"
#else
# define MODNAME "posix"
#endif

When posix_module_exec runs it sets sys.modules["posix"] (or "nt") to the newly-constructed module, then imports either posixpath or ntpath and binds the result to os.path on the Python side. The C code does not implement any path manipulation; it only wires up the delegation.

// CPython: Modules/posixmodule.c:15850 posix_module_exec path setup
static int
posix_module_exec(PyObject *module)
{
...
#ifdef MS_WINDOWS
if (PyModule_AddObject(module, "path", import_and_init("ntpath")) < 0)
return -1;
#else
if (PyModule_AddObject(module, "path", import_and_init("posixpath")) < 0)
return -1;
#endif
...
}

gopy notes

Status: not yet ported.

Planned package path: module/os/

The stat_result struct should map to a Go struct implementing the structseq protocol via objects/. The os_open_impl / os_read_impl / os_write_impl triad are direct wrappers around Go's os.OpenFile, (*os.File).Read, and (*os.File).Write, but the gopy layer must use raw file descriptors (via os.NewFile) to match CPython's fd-based API rather than the Go file handle model.

The scandir iterator is the highest-complexity item. It requires a Go struct holding an *os.File (used as a directory handle), with iternext calling (*os.File).Readdir(1) or the lower-level syscall.ReadDirent to preserve the d_type field needed for DirEntry.is_dir() without a stat call.

The #ifdef MS_WINDOWS name split is not needed in gopy because the os package abstracts over platforms, but the os.path delegation should still point to the appropriate pure-Python path module for the current OS.